Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .flowconfig
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[ignore]
.*node_modules/babel.*
.*node_modules/fbjs.*
.*node_modules/json5.*

[include]

Expand Down
2 changes: 2 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
ISC License

Copyright (c) 2016, Simon Sturmer <sstur@me.com>

Permission to use, copy, modify, and/or distribute this software for any
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
49 changes: 32 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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"
Expand Down
46 changes: 46 additions & 0 deletions src/__tests__/utilities-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/* @flow */
const {describe, it} = global;
import expect from 'expect';
import {
encodeContent,
encodeURL,
escapeTitle,
} from '../utilities';

describe('utilities', () => {
describe('encodeContent', () => {
it('should escape the * character', () => {
var result = encodeContent('Test * String ** Testing*');
expect(result).toEqual('Test \\* String \\*\\* Testing\\*');
});

it('should escape the _ character', () => {
var result = encodeContent('Test _ String __ Testing_');
expect(result).toEqual('Test \\_ String \\_\\_ Testing\\_');
});

it('should escape the ` character', () => {
var result = encodeContent('Test ` String `` Testing`');
expect(result).toEqual('Test \\` String \\`\\` Testing\\`');
});

it('should escape *, _, and ` characters', () => {
var result = encodeContent('Test ` String ** Testing_');
expect(result).toEqual('Test \\` String \\*\\* Testing\\_');
});
});

describe('encodeURL', () => {
it('should escape the ) character', () => {
var result = encodeURL('https://google.com/hello)');
expect(result).toEqual('https://google.com/hello%29');
});
});

describe('escapeTitle', () => {
it('should escape the " character', () => {
var result = escapeTitle('Test "Hello" Test');
expect(result).toEqual('Test \\"Hello\\" Test');
});
});
});
42 changes: 26 additions & 16 deletions src/stateToMarkdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ import {
BLOCK_TYPE,
ENTITY_TYPE,
INLINE_STYLE,
} from 'draft-js-tools';
} from 'draft-js-utils';
import {Entity} from 'draft-js';
import {
encodeContent,
encodeURL,
escapeTitle,
} from './utilities';

import type {ContentState, ContentBlock} from 'draft-js';

Expand Down Expand Up @@ -63,6 +68,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();
Expand Down Expand Up @@ -210,6 +230,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;
}
Expand All @@ -227,21 +252,6 @@ function canHaveDepth(blockType: any): boolean {
}
}

function encodeContent(text) {
return text.replace(/[*_`]/g, '\\$&');
}

// Encode chars that would normally be allowed in a URL but would conflict with
// our markdown syntax: `[foo](http://foo/)`
function encodeURL(url) {
return url.replace(/\)/g, '%29');
}

// Escape quotes using backslash.
function escapeTitle(text) {
return text.replace(/"/g, '\\"');
}

export default function stateToMarkdown(content: ContentState): string {
return new MarkupGenerator(content).generate();
}
32 changes: 32 additions & 0 deletions src/utilities.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* @flow */
const BASIC_MARKDOWN_CHARS = /[*_`]/g;
const URL_MARKDOWN_CHARS = /\)/g;
const QUOTATIONS = /"/g;

/**
* Escape chars (*, _) that have meaning in markdown so that they aren't interpreted as part of the markdown
* @param {string} text - the text to be replaced.
* @returns {string}
*/
export function encodeContent(text: string) {
return text.replace(BASIC_MARKDOWN_CHARS, '\\$&');
}

/**
* Encode chars that would normally be allowed in a URL but would conflict with
* our markdown syntax: `[foo](http://foo/)`
* @param {string} url The url to be encoded.
* @returns {string}
*/
export function encodeURL(url: string) {
return url.replace(URL_MARKDOWN_CHARS, '%29');
}

/**
* Escape quotes using backslash
* @param {string} text The string to be escaped
* @returns {string}
*/
export function escapeTitle(text: string) {
return text.replace(QUOTATIONS, '\\"');
}
1 change: 0 additions & 1 deletion test/mocha.opts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
--compilers js:babel-core/register
src/__tests__/*.js src/**/__tests__/*.js
8 changes: 8 additions & 0 deletions test/test-cases.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ _**BoldItalic**_
{"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).
Expand Down