diff --git a/.flowconfig b/.flowconfig index 6f52e39..0575b6a 100644 --- a/.flowconfig +++ b/.flowconfig @@ -1,6 +1,7 @@ [ignore] .*node_modules/babel.* .*node_modules/fbjs.* +.*node_modules/json5.* [include] diff --git a/LICENSE b/LICENSE index 2684d70..9197c59 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,5 @@ +ISC License + Copyright (c) 2016, Simon Sturmer Permission to use, copy, modify, and/or distribute this software for any diff --git a/README.md b/README.md index cd2d83c..6fe4451 100644 --- a/README.md +++ b/README.md @@ -12,4 +12,4 @@ This project is still under development. If you want to help out, please open an ## License -This software is [BSD Licensed](/LICENSE). +This software is [ISC Licensed](/LICENSE). diff --git a/package.json b/package.json index 7017cf1..1ed5b17 100644 --- a/package.json +++ b/package.json @@ -1,35 +1,40 @@ { "name": "draft-js-export-markdown", - "version": "0.1.2", + "version": "0.2.0", "description": "DraftJS: Export ContentState to Markdown", "main": "lib/main.js", "scripts": { "build": "babel src --ignore '_*' --out-dir lib", "lint": "eslint --max-warnings 0 .", + "typecheck": "flow", "prepublish": "npm run build", - "test": "npm run lint && npm run test-src", - "test-src": "mocha" + "test": "npm run lint && npm run typecheck && npm run test-src", + "test-src": "mocha src/__tests__/*.js src/**/__tests__/*.js" }, "dependencies": { - "draft-js-tools": "^0.1.2" + "draft-js-utils": "^0.1.5" + }, + "peerDependencies": { + "draft-js": ">=0.5.0", + "immutable": "3.x.x" }, "devDependencies": { - "babel-core": "^6.7.2", - "babel-eslint": "^5.0.0", - "babel-plugin-transform-class-properties": "^6.6.0", - "babel-preset-es2015": "^6.6.0", + "babel-cli": "^6.9.0", + "babel-core": "^6.9.0", + "babel-eslint": "^6.0.4", + "babel-plugin-transform-class-properties": "^6.9.0", + "babel-preset-es2015": "^6.9.0", "babel-preset-react": "^6.5.0", "babel-preset-stage-2": "^6.5.0", - "draft-js": "^0.2.2", - "eslint": "2.2.0", - "eslint-plugin-babel": "^3.1.0", - "eslint-plugin-flow-vars": "^0.2.1", - "eslint-plugin-react": "^4.2.1", - "expect": "^1.15.2", - "immutable": "^3.7.6", + "eslint": "^2.10.2", + "eslint-plugin-babel": "^3.2.0", + "eslint-plugin-flow-vars": "^0.4.0", + "eslint-plugin-react": "^5.1.1", + "expect": "^1.20.1", + "flow-bin": "^0.25.0", "mocha": "^2.4.5", - "react": "^0.14.7", - "react-dom": "^0.14.7" + "react": "^15.0.2", + "react-dom": "^15.0.2" }, "repository": { "type": "git", @@ -40,6 +45,16 @@ "export-markdown" ], "author": "sstur@me.com", + "contributors": [ + { + "name": "Freddy Harris", + "url": "https://github.com/Freddy03h" + }, + { + "name": "Simon Sturmer", + "url": "https://github.com/sstur" + } + ], "license": "ISC", "bugs": { "url": "https://github.com/sstur/draft-js-export-markdown/issues" diff --git a/src/stateToMarkdown.js b/src/stateToMarkdown.js index 1bfe776..fbb1072 100644 --- a/src/stateToMarkdown.js +++ b/src/stateToMarkdown.js @@ -5,7 +5,7 @@ import { BLOCK_TYPE, ENTITY_TYPE, INLINE_STYLE, -} from 'draft-js-tools'; +} from 'draft-js-utils'; import {Entity} from 'draft-js'; import type {ContentState, ContentBlock} from 'draft-js'; @@ -19,6 +19,7 @@ const { } = INLINE_STYLE; const CODE_INDENT = ' '; +const STRIP_WHITESPACE = /([\s]*)([\W\w]*[^\s]+)([\s]*)/; class MarkupGenerator { blocks: Array; @@ -63,6 +64,21 @@ class MarkupGenerator { this.output.push('### ' + this.renderBlockContent(block) + '\n'); break; } + case BLOCK_TYPE.HEADER_FOUR: { + this.insertLineBreaks(1); + this.output.push('#### ' + this.renderBlockContent(block) + '\n'); + break; + } + case BLOCK_TYPE.HEADER_FIVE: { + this.insertLineBreaks(1); + this.output.push('##### ' + this.renderBlockContent(block) + '\n'); + break; + } + case BLOCK_TYPE.HEADER_SIX: { + this.insertLineBreaks(1); + this.output.push('###### ' + this.renderBlockContent(block) + '\n'); + break; + } case BLOCK_TYPE.UNORDERED_LIST_ITEM: { let blockDepth = block.getDepth(); let lastBlock = this.getLastBlock(); @@ -186,21 +202,21 @@ class MarkupGenerator { } let content = encodeContent(text); if (style.has(BOLD)) { - content = `**${content}**`; + content = content.replace(STRIP_WHITESPACE, '$1**$2**$3'); } if (style.has(UNDERLINE)) { // TODO: encode `+`? - content = `++${content}++`; + content = content.replace(STRIP_WHITESPACE, '$1++$2++$3'); } if (style.has(ITALIC)) { - content = `_${content}_`; + content = content.replace(STRIP_WHITESPACE, '$1_$2_$3'); } if (style.has(STRIKETHROUGH)) { // TODO: encode `~`? - content = `~~${content}~~`; + content = content.replace(STRIP_WHITESPACE, '$1~~$2~~$3'); } if (style.has(CODE)) { - content = (blockType === BLOCK_TYPE.CODE) ? content : '`' + content + '`'; + content = (blockType === BLOCK_TYPE.CODE) ? content : content.replace(STRIP_WHITESPACE, '$1`$2`$3'); } return content; }).join(''); @@ -210,6 +226,11 @@ class MarkupGenerator { let url = data.url || ''; let title = data.title ? ` "${escapeTitle(data.title)}"` : ''; return `[${content}](${encodeURL(url)}${title})`; + } else if (entity != null && entity.getType() === ENTITY_TYPE.IMAGE) { + let data = entity.getData(); + let src = data.src || ''; + let alt = data.alt ? ` "${escapeTitle(data.alt)}"` : ''; + return `![${alt}](${encodeURL(src)})`; } else { return content; } diff --git a/test/mocha.opts b/test/mocha.opts index 67c5129..ccd1f09 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,2 +1 @@ --compilers js:babel-core/register -src/__tests__/*.js src/**/__tests__/*.js diff --git a/test/test-cases.txt b/test/test-cases.txt index baca361..1ab30a4 100644 --- a/test/test-cases.txt +++ b/test/test-cases.txt @@ -6,14 +6,30 @@ a {"entityMap":{},"blocks":[{"key":"99n0j","text":"asdf","type":"unstyled","depth":0,"inlineStyleRanges":[{"offset":3,"length":1,"style":"BOLD"}],"entityRanges":[]}]} asd**f** +>> Inline style with whitespace +{"entityMap":{},"blocks":[{"key":"99n0j","text":"asdf ","type":"unstyled","depth":0,"inlineStyleRanges":[{"offset":0,"length":6,"style":"BOLD"}],"entityRanges":[]}]} +**asdf** + >> Nested Inline Style {"entityMap":{},"blocks":[{"key":"9nc73","text":"BoldItalic","type":"unstyled","depth":0,"inlineStyleRanges":[{"offset":0,"length":10,"style":"BOLD"},{"offset":0,"length":10,"style":"ITALIC"}],"entityRanges":[]}]} _**BoldItalic**_ +>> Nested Inline Style with whitespace +{"entityMap":{},"blocks":[{"key":"9nc73","text":"BoldItalic ","type":"unstyled","depth":0,"inlineStyleRanges":[{"offset":0,"length":14,"style":"BOLD"},{"offset":0,"length":10,"style":"ITALIC"}],"entityRanges":[]}]} +_**BoldItalic**_ + >> Adjacent Inline Style {"entityMap":{},"blocks":[{"key":"9nc73","text":"BoldItalic","type":"unstyled","depth":0,"inlineStyleRanges":[{"offset":4,"length":6,"style":"BOLD"},{"offset":0,"length":4,"style":"ITALIC"}],"entityRanges":[]}]} _Bold_**Italic** +>> Image with alt +{"entityMap":{"0":{"type":"IMAGE","mutability":"MUTABLE","data":{"src":"/a.jpg","alt":"x"}}},"blocks":[{"key":"f131g","text":"Hello World.","type":"unstyled","depth":0,"inlineStyleRanges":[],"entityRanges":[{"offset":5,"length":1,"key":0}]}]} +Hello![ "x"](/a.jpg)World. + +>> Image with empty alt +{"entityMap":{"0":{"type":"IMAGE","mutability":"MUTABLE","data":{"src":"/a.jpg","alt":""}}},"blocks":[{"key":"f131g","text":"Hello World.","type":"unstyled","depth":0,"inlineStyleRanges":[],"entityRanges":[{"offset":5,"length":1,"key":0}]}]} +Hello![](/a.jpg)World. + >> Link without title {"entityMap":{"0":{"type":"LINK","mutability":"MUTABLE","data":{"url":"/a","foo":"x"}}},"blocks":[{"key":"f131g","text":"Hello World.","type":"unstyled","depth":0,"inlineStyleRanges":[],"entityRanges":[{"offset":6,"length":5,"key":0}]}]} Hello [World](/a).