├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .nvmrc ├── .travis.yml ├── LICENSE ├── README.md ├── bower.json ├── content ├── all.md ├── audio.md ├── code.md ├── diff.md ├── flow.md ├── image.md ├── maps.md ├── railroad.md ├── sequence.md ├── snippets.md ├── tasklist.md ├── tex.md └── video.md ├── demo ├── app.jsx ├── index.jsx ├── sidebar.jsx └── style.scss ├── deploy.sh ├── devserver.js ├── index.html ├── lib ├── codeblock.js ├── content.js ├── editor │ ├── editor.js │ ├── index.js │ ├── split.scss │ ├── style.scss │ └── toolbar.js ├── embed.js ├── index.js ├── inject.js ├── linkify.js ├── markdown.js ├── parseuri.js ├── railroad.js ├── regex.js ├── render.js ├── style.scss └── tasklist.js ├── make.cmd ├── makesite.sh ├── markdown.iml ├── package.json ├── src ├── codeblock.jsx ├── content.jsx ├── editor │ ├── editor.jsx │ ├── index.js │ ├── split.scss │ ├── style.scss │ └── toolbar.jsx ├── embed.jsx ├── index.js ├── inject.js ├── linkify.js ├── markdown.jsx ├── parseuri.js ├── railroad.js ├── regex.js ├── render.js ├── style.scss └── tasklist.jsx └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react", "stage-0"], 3 | "env": { 4 | "development": { 5 | "presets": ["react-hmre"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = crlf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules/ 2 | **/bower_components/ 3 | lib 4 | static 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parser": "babel-eslint", 4 | 5 | "env": { 6 | "browser": true, 7 | "node": true, 8 | "mocha": true, 9 | "es6": true 10 | }, 11 | 12 | "globals": { 13 | "$": true 14 | }, 15 | 16 | "rules": { 17 | "id-length": 0, 18 | "indent": ["error", 2, {"SwitchCase": 1}], 19 | "no-console": 0, 20 | "func-names": 0, 21 | "linebreak-style": 0, 22 | "import/no-extraneous-dependencies": 0, 23 | "react/no-multi-comp": 0, 24 | "react/prop-types": 0, 25 | "react/jsx-indent": ["error", 2] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | npm-debug.log 4 | static 5 | site 6 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 6.1.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 6.1.0 4 | env: 5 | global: 6 | - GH_REF: github.com/reactbits/markdown.git 7 | - secure: CCdnqhazdzZUJpwbRE0OZwpzwZT8JIt1xGedOIIpKFTteW9noIJuuCvMC6+b0hH4KLANx/mTk/iHSwYqHMkrwcoy8CqeYe4/QmMswJvLeWz/rvyPLefF1pnUZFSqzhGgXwHP0CVfZVvoRuwYg1x6eiyq4nkSBAE/ryQ+cqZvHPfScXO6S1Z9BbKhQ0IPoAQTtTphhhBHHEuOVQHjvErkQdiH2pzHmZyWynenzItrSUIY2YUlVmv9UM4ltthof4vR3RVu/IQO1lSZsqOP4ynjnIMFq6jMIc2ZjZJnqKDQ/wmZeaKl6GbVPraZa4VSntANcsP7lY2wJqPM38vlYMjSk+aEdqBvksnDSnUwK9ppFTPFK3OWOg4gaZu+dzgZCZI07eI2S7rjT9tGUwZGtr1dcklOvylrbx2Qyv8/J3tRYoOABj4u9oPb+STGduYOs/gxh4vrhp+KCN2Zx24BqfHkY2DIsBhULhGw8K1NWCxhlsh3O3Uyra5PLbX0OS1+KjGLWNOBweChD/EVwd9YNJagPoLrKvnLEXg58iw1gSPGnMtg39YHcWjrqnJLVJBV8oyDmhpOrX8Tzrnl6DXhjmslsx53U4OWH9F8RDk/uMIQvnPeAQasxVwKTAA4/VW8eBnXcXR9Tb4JUg8TQwUCb2cVQK/+KzxKLyQim0pSJzcZfzE= 8 | script: 9 | - npm run lint 10 | - npm test 11 | - bash ./deploy.sh 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 reactbits 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://badge.fury.io/js/react-markdown2.svg)](https://badge.fury.io/js/react-markdown2) 2 | [![Build Status](https://travis-ci.org/reactbits/markdown.svg?branch=master)](https://travis-ci.org/reactbits/markdown) 3 | [![Total downloads](https://img.shields.io/npm/dt/react-markdown2.svg)](https://www.npmjs.com/package/react-markdown2) 4 | 5 | [![Dependency Status](https://david-dm.org/reactbits/markdown.svg)](https://david-dm.org/reactbits/markdown) 6 | [![devDependency Status](https://david-dm.org/reactbits/markdown/dev-status.svg)](https://david-dm.org/reactbits/markdown#info=devDependencies) 7 | 8 | # markdown 9 | Yet another react component to render markdown. 10 | 11 | ## Install 12 | 13 | ``` 14 | npm install --save react-markdown2 15 | ``` 16 | 17 | ## Usage 18 | 19 | ```jsx 20 | import React, {Component} from "react"; 21 | import Markdown from "react-markdown2"; 22 | 23 | class Example extends Component { 24 | render() { 25 | return ( 26 |
27 | {/* Pass Markdown source to the `source` prop */} 28 | 29 | 30 | {/* Or pass it as children */} 31 | {/* You can nest React components, too */} 32 | 33 | {` 34 | ## Header 35 | 1. One 36 | 2. Two 37 | `} 38 | 39 |
Nested component
40 | 41 | {`Test`} 42 |
43 |
44 | ) 45 | } 46 | } 47 | ``` 48 | 49 | ## Webpack build pipeline 50 | 51 | See [webpack.config.js](https://github.com/reactbits/markdown/blob/master/webpack.config.js) as an example how to integrate this package into your webpack pipeline, i.e. 52 | you need to use the following webpack plugins: 53 | * extract-text-webpack-plugin 54 | * style-loader 55 | * sass-loader 56 | * css-loader 57 | * postcss-loader 58 | * webpack-sources 59 | 60 | ## TODO 61 | * [x] Use markdown-it to render markdown content 62 | * [x] Use prismjs for syntax highlighting 63 | * [ ] Diagrams 64 | * [x] [Sequance diagrams](https://github.com/bramp/js-sequence-diagrams) 65 | * [x] [Flowchart](https://github.com/adrai/flowchart.js) 66 | * [x] [Railroad diagrams](https://github.com/tabatkins/railroad-diagrams) 67 | * [ ] Buttons to show source code of diagram 68 | * [ ] [KaTeX](https://github.com/Khan/KaTeX) 69 | * [x] basic integration 70 | * [ ] import katex css 71 | * [ ] MathJax 72 | * [ ] Auto-embeddable links 73 | * [ ] Video services 74 | * [x] youtube 75 | * [x] vimeo 76 | * [x] vine 77 | * [x] daylymotion 78 | * [ ] viddler 79 | * [ ] blip.tv 80 | * [ ] liveleak 81 | * [x] ted 82 | * [ ] ustream 83 | * [ ] Audio services 84 | * [x] soundcloud 85 | * [ ] Code snippets 86 | * [ ] github gist 87 | * [ ] codepen 88 | * [ ] ideone 89 | * [ ] jsbin 90 | * [ ] jsfiddle 91 | * [ ] plunker 92 | * [ ] Social media 93 | * [ ] twitter 94 | * [ ] facebook 95 | * [ ] Maps 96 | * [x] google maps 97 | * [x] Unified diff fence blocks 98 | * [ ] Automatically detect and render diff patch blocks 99 | * [ ] Integrate/implement [embed.js](https://github.com/ritz078/embed.js) features 100 | * [ ] render HTML content if content-type is not markdown 101 | * [ ] allow to extend markdown rendering with markdown-it plugins 102 | 103 | ## Licence 104 | 105 | MIT. See LICENSE. 106 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-markdown2", 3 | "version": "0.9.0", 4 | "authors": [ 5 | "Sergey Todyshev (stodyshev@gmail.com)" 6 | ], 7 | "description": "React markdown component", 8 | "moduleType": [ 9 | "es6", 10 | "globals", 11 | "node" 12 | ], 13 | "private": true, 14 | "ignore": [ 15 | "**/.*", 16 | "node_modules" 17 | ], 18 | "dependencies": { 19 | "KaTeX": "katex#~0.5.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /content/all.md: -------------------------------------------------------------------------------- 1 | ### Features 2 | 3 | - Support Standard Markdown / CommonMark and GFM(GitHub Flavored Markdown); 4 | - Markdown Extras : Emoji, Task lists, @Links...; 5 | 6 | # H1 header 7 | ## H2 header 8 | ### H3 header 9 | #### H4 header 10 | ##### H5 header 11 | ###### H6 header 12 | # Heading 1 link [Heading link](https://github.com/pandao/editor.md "Heading link") 13 | ## Heading 2 link [Heading link](https://github.com/pandao/editor.md "Heading link") 14 | ### Heading 3 link [Heading link](https://github.com/pandao/editor.md "Heading link") 15 | #### Heading 4 link [Heading link](https://github.com/pandao/editor.md "Heading link") Heading link [Heading link](https://github.com/pandao/editor.md "Heading link") 16 | ##### Heading 5 link [Heading link](https://github.com/pandao/editor.md "Heading link") 17 | ###### Heading 6 link [Heading link](https://github.com/pandao/editor.md "Heading link") 18 | 19 | ## Headers (Underline) 20 | 21 | H1 Header (Underline) 22 | ============= 23 | 24 | H2 Header (Underline) 25 | ------------- 26 | 27 | ### Characters 28 | 29 | ---- 30 | 31 | ~~Strikethrough~~ Strikethrough (when enable html tag decode.) 32 | *Italic* _Italic_ 33 | **Emphasis** __Emphasis__ 34 | ***Emphasis Italic*** ___Emphasis Italic___ 35 | 36 | Superscript: X2,Subscript: O2 37 | 38 | **Abbreviation(link HTML abbr tag)** 39 | 40 | The HTML specification is maintained by the W3C. 41 | 42 | ### Blockquotes 43 | 44 | > Blockquotes 45 | 46 | Paragraphs and Line Breaks 47 | 48 | > "Blockquotes Blockquotes", [Link](http://localhost/)。 49 | 50 | ### Links 51 | 52 | [Links](http://localhost/) 53 | 54 | [Links with title](http://localhost/ "link title") 55 | 56 | `` : 57 | 58 | [Reference link][id/name] 59 | 60 | [id/name]: http://link-url/ 61 | 62 | GFM a-tail link @pandao 63 | 64 | ### Code Blocks (multi-language) & highlighting 65 | 66 | #### Inline code 67 | 68 | `$ npm install npm@latest` 69 | 70 | #### Code Blocks (Indented style) 71 | 72 | Indented 4 spaces, like `
` (Preformatted Text).
 73 | 
 74 |     
 77 | 
 78 | Code Blocks (Preformatted text):
 79 | 
 80 |     | First Header  | Second Header |
 81 |     | ------------- | ------------- |
 82 |     | Content Cell  | Content Cell  |
 83 |     | Content Cell  | Content Cell  |
 84 | 
 85 | #### Javascript 
 86 | 
 87 | ```javascript
 88 | function test(){
 89 | 	console.log("Hello world!");
 90 | }
 91 | 
 92 | (function(){
 93 |     var box = function(){
 94 |         return box.fn.init();
 95 |     };
 96 | 
 97 |     box.prototype = box.fn = {
 98 |         init : function(){
 99 |             console.log('box.init()');
100 | 
101 | 			return this;
102 |         },
103 | 
104 | 		add : function(str){
105 | 			alert("add", str);
106 | 
107 | 			return this;
108 | 		},
109 | 
110 | 		remove : function(str){
111 | 			alert("remove", str);
112 | 
113 | 			return this;
114 | 		}
115 |     };
116 | 
117 |     box.fn.init.prototype = box.fn;
118 | 
119 |     window.box = box;
120 | })();
121 | 
122 | var testBox = box();
123 | testBox.add("jQuery").remove("jQuery");
124 | ```
125 | 
126 | #### HTML code
127 | 
128 | ```html
129 | 
130 | 
131 |     
132 |         
133 |         Hello world!
134 |     
135 |     
136 |         

Hello world!

137 | 138 | 139 | ``` 140 | 141 | ### Images 142 | 143 | Image: 144 | 145 | ![](https://pandao.github.io/editor.md/examples/images/4.jpg) 146 | 147 | > Follow your heart. 148 | 149 | ---- 150 | 151 | ### Lists 152 | 153 | #### Unordered list (-) 154 | 155 | - Item A 156 | - Item B 157 | - Item C 158 | 159 | #### Unordered list (*) 160 | 161 | * Item A 162 | * Item B 163 | * Item C 164 | 165 | #### Unordered list (plus sign and nested) 166 | 167 | + Item A 168 | + Item B 169 | + Item B 1 170 | + Item B 2 171 | + Item B 3 172 | + Item C 173 | * Item C 1 174 | * Item C 2 175 | * Item C 3 176 | 177 | #### Ordered list 178 | 179 | 1. Item A 180 | 2. Item B 181 | 3. Item C 182 | 183 | ---- 184 | 185 | ### Tables 186 | 187 | First Header | Second Header 188 | ------------- | ------------- 189 | Content Cell | Content Cell 190 | Content Cell | Content Cell 191 | 192 | | First Header | Second Header | 193 | | ------------- | ------------- | 194 | | Content Cell | Content Cell | 195 | | Content Cell | Content Cell | 196 | 197 | | Function name | Description | 198 | | ------------- | ------------------------------ | 199 | | `help()` | Display the help window. | 200 | | `destroy()` | **Destroy your computer!** | 201 | 202 | | Item | Value | 203 | | --------- | -----:| 204 | | Computer | $1600 | 205 | | Phone | $12 | 206 | | Pipe | $1 | 207 | 208 | | Left-Aligned | Center Aligned | Right Aligned | 209 | | :------------ |:---------------:| -----:| 210 | | col 3 is | some wordy text | $1600 | 211 | | col 2 is | centered | $12 | 212 | | zebra stripes | are neat | $1 | 213 | 214 | ---- 215 | 216 | #### HTML entities 217 | 218 | © & ¨ ™ ¡ £ 219 | & < > ¥ € ® ± ¶ § ¦ ¯ « · 220 | 221 | X² Y³ ¾ ¼ × ÷ » 222 | 223 | 18ºC " ' 224 | 225 | ## Escaping for Special Characters 226 | 227 | \*literal asterisks\* 228 | 229 | ## Markdown extras 230 | 231 | ### GFM task list 232 | 233 | - [x] GFM task list 1 234 | - [x] GFM task list 2 235 | - [ ] GFM task list 3 236 | - [ ] GFM task list 3-1 237 | - [ ] GFM task list 3-2 238 | - [ ] GFM task list 3-3 239 | - [ ] GFM task list 4 240 | - [ ] GFM task list 4-1 241 | - [ ] GFM task list 4-2 242 | 243 | ### Emoji mixed :smiley: 244 | 245 | > Blockquotes :star: 246 | 247 | #### GFM task lists & Emoji & fontAwesome icon emoji & editormd logo emoji :editormd-logo-5x: 248 | 249 | - [x] :smiley: @mentions, :smiley: #refs, [links](), **formatting**, and tags supported :editormd-logo:; 250 | - [x] list syntax required (any unordered or ordered list supported) :editormd-logo-3x:; 251 | - [x] [ ] :smiley: this is a complete item :smiley:; 252 | - [ ] []this is an incomplete item [test link](#) :fa-star: @pandao; 253 | - [ ] [ ]this is an incomplete item :fa-star: :fa-gear:; 254 | - [ ] :smiley: this is an incomplete item [test link](#) :fa-star: :fa-gear:; 255 | - [ ] :smiley: this is :fa-star: :fa-gear: an incomplete item [test link](#); 256 | 257 | ### TeX(LaTeX) 258 | 259 | $$E=mc^2$$ 260 | 261 | Inline $$E=mc^2$$ Inline,Inline $$E=mc^2$$ Inline。 262 | 263 | $$\(\sqrt{3x-1}+(1+x)^2\)$$ 264 | 265 | $$\sin(\alpha)^{\theta}=\sum_{i=0}^{n}(x^i + \cos(f))$$ 266 | 267 | ### Flow chart 268 | 269 | ```flow 270 | st=>start: Login 271 | op=>operation: Login operation 272 | cond=>condition: Successful Yes or No? 273 | e=>end: To admin 274 | 275 | st->op->cond 276 | cond(yes)->e 277 | cond(no)->op 278 | ``` 279 | 280 | ### Sequence diagram 281 | 282 | ```seq 283 | Andrew->China: Says Hello 284 | Note right of China: China thinks\nabout it 285 | China-->Andrew: How are you? 286 | Andrew->>China: I am good thanks! 287 | ``` 288 | 289 | ### End 290 | -------------------------------------------------------------------------------- /content/audio.md: -------------------------------------------------------------------------------- 1 | ### SoundCloud 2 | 3 | https://soundcloud.com/m24kermit/papa-roach-last-resort-lyrics 4 | 5 | ### Spotify 6 | -------------------------------------------------------------------------------- /content/code.md: -------------------------------------------------------------------------------- 1 | #### Javascript  2 | 3 | ```javascript 4 | function test(){ 5 | console.log("Hello world!"); 6 | } 7 | 8 | (function(){ 9 | var box = function(){ 10 | return box.fn.init(); 11 | }; 12 | 13 | box.prototype = box.fn = { 14 | init : function(){ 15 | console.log('box.init()'); 16 | 17 | return this; 18 | }, 19 | 20 | add : function(str){ 21 | alert("add", str); 22 | 23 | return this; 24 | }, 25 | 26 | remove : function(str){ 27 | alert("remove", str); 28 | 29 | return this; 30 | } 31 | }; 32 | 33 | box.fn.init.prototype = box.fn; 34 | 35 | window.box = box; 36 | })(); 37 | 38 | var testBox = box(); 39 | testBox.add("jQuery").remove("jQuery"); 40 | ``` 41 | 42 | #### HTML code 43 | 44 | ```html 45 | 46 | 47 | 48 | 49 | Hello world! 50 | 51 | 52 |

Hello world!

53 | 54 | 55 | ``` 56 | -------------------------------------------------------------------------------- /content/diff.md: -------------------------------------------------------------------------------- 1 | ### Unified diffs 2 | 3 | ```diff 4 | diff --git a/index.html b/index.html 5 | index d80dce7..01922d9 100644 6 | --- a/index.html 7 | +++ b/index.html 8 | @@ -8,6 +8,7 @@ 9 | 10 | 11 | 12 | + 13 | 14 | 15 | 16 | diff --git a/lib/change.js b/lib/change.js 17 | index 98f814c..7f1d473 100644 18 | --- a/lib/change.js 19 | +++ b/lib/change.js 20 | @@ -13,18 +13,23 @@ var _style = require('./style'); 21 | 22 | var _style2 = _interopRequireDefault(_style); 23 | 24 | +var _prismjsPackage = require('prismjs-package'); 25 | + 26 | +var _prismjsPackage2 = _interopRequireDefault(_prismjsPackage); 27 | + 28 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 29 | 30 | function Change(props) { 31 | var ln1 = props.normal ? props.ln1 : props.ln; 32 | var ln2 = props.normal ? props.ln2 : props.ln; 33 | 34 | - // TODO highlight with prism.js 35 | var html = props.content; 36 | 37 | - // try { 38 | - // html = highlight(props.lang, props.content).value; 39 | - // } catch (e) {} 40 | + try { 41 | + html = (0, _prismjsPackage2.default)(props.content, props.lang); 42 | + } catch (e) { 43 | + console.log('highlight error:', e); 44 | + } 45 | 46 | return _react2.default.createElement( 47 | 'tr', 48 | diff --git a/lib/diffview.js b/lib/diffview.js 49 | index c2627f7..05f09d3 100644 50 | --- a/lib/diffview.js 51 | +++ b/lib/diffview.js 52 | @@ -47,7 +47,7 @@ function Part(props) { 53 | 54 | return _react2.default.createElement( 55 | 'article', 56 | - null, 57 | + { className: _style2.default.diff }, 58 | _react2.default.createElement( 59 | 'header', 60 | null, 61 | diff --git a/package.json b/package.json 62 | index 8940bd8..60eeb02 100644 63 | --- a/package.json 64 | +++ b/package.json 65 | @@ -28,7 +28,8 @@ 66 | "classnames": "^2.2.3", 67 | "lang-map": "^0.4.0", 68 | "lodash": "^4.0.0", 69 | - "parse-diff": "^0.3.1", 70 | + "parse-diff": "^0.3.2", 71 | + "prismjs-package": "^0.6.1", 72 | "react": "^0.14.6", 73 | "react-dom": "^0.14.6" 74 | }, 75 | diff --git a/src/change.js b/src/change.js 76 | index 1a78188..f86ea4d 100644 77 | --- a/src/change.js 78 | +++ b/src/change.js 79 | @@ -1,16 +1,18 @@ 80 | import React from 'react'; 81 | import style from './style'; 82 | +import highlight from 'prismjs-package'; 83 | 84 | export default function Change(props) { 85 | const ln1 = props.normal ? props.ln1 : props.ln; 86 | const ln2 = props.normal ? props.ln2 : props.ln; 87 | 88 | - // TODO highlight with prism.js 89 | - const html = props.content; 90 | + let html = props.content; 91 | 92 | - // try { 93 | - // html = highlight(props.lang, props.content).value; 94 | - // } catch (e) {} 95 | + try { 96 | + html = highlight(props.content, props.lang); 97 | + } catch (e) { 98 | + console.log('highlight error:', e); 99 | + } 100 | 101 | return ( 102 | 103 | diff --git a/src/diffview.js b/src/diffview.js 104 | index 20e5262..b579a44 100644 105 | --- a/src/diffview.js 106 | +++ b/src/diffview.js 107 | @@ -17,7 +17,7 @@ function Part(props) { 108 | }); 109 | 110 | return ( 111 | -
112 | +
113 |
114 | +++ {additions} 115 | --- {deletions} 116 | ``` 117 | -------------------------------------------------------------------------------- /content/flow.md: -------------------------------------------------------------------------------- 1 | ### Flow charts 2 | 3 | ```flow 4 | st=>start: Login 5 | op=>operation: Login operation 6 | cond=>condition: Successful Yes or No? 7 | e=>end: To admin 8 | 9 | st->op->cond 10 | cond(yes)->e 11 | cond(no)->op 12 | ``` 13 | -------------------------------------------------------------------------------- /content/image.md: -------------------------------------------------------------------------------- 1 | ### instagram 2 | 3 | http://instagram.com/p/dnQi4EGuZx/ 4 | 5 | ### flickr 6 | 7 | https://www.flickr.com/photos/133837486@N02/21077148959/ 8 | 9 | ### slideshare 10 | 11 | http://www.slideshare.net/briansolis/26-disruptive-technology-trends-2016-2018-56796196 12 | -------------------------------------------------------------------------------- /content/maps.md: -------------------------------------------------------------------------------- 1 | ### Google maps 2 | 3 | https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d387144.0075834208!2d-73.97800349999999!3d40.7056308!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x89c24fa5d33f083b%3A0xc80b8f06e177fe62!2sNew+York%2C+NY!5e0!3m2!1sen!2sus!4v1394298866288 4 | -------------------------------------------------------------------------------- /content/railroad.md: -------------------------------------------------------------------------------- 1 | ### Railroad diagrams 2 | 3 | ```railroad 4 | Diagram( 5 | Optional('+', 'skip'), 6 | Choice(0, 7 | NonTerminal('name-start char'), 8 | NonTerminal('escape')), 9 | ZeroOrMore( 10 | Choice(0, 11 | NonTerminal('name char'), 12 | NonTerminal('escape')))) 13 | ``` 14 | -------------------------------------------------------------------------------- /content/sequence.md: -------------------------------------------------------------------------------- 1 | ### Sequence diagrams 2 | 3 | ```seq 4 | Andrew->China: Says Hello 5 | Note right of China: China thinks\nabout it 6 | China-->Andrew: How are you? 7 | Andrew->>China: I am good thanks! 8 | ``` 9 | -------------------------------------------------------------------------------- /content/snippets.md: -------------------------------------------------------------------------------- 1 | ### Gist 2 | 3 | https://gist.github.com/sergeyt/173f8a5dff83a3b5858a 4 | 5 | ### CodePen 6 | 7 | http://codepen.io/vinsongrant/pen/NxgEMz 8 | -------------------------------------------------------------------------------- /content/tasklist.md: -------------------------------------------------------------------------------- 1 | ### Task lists 2 | 3 | - [ ] task 1 4 | - [x] task 2 5 | - [ ] task 3 6 | * [ ] task 3-1 7 | * [x] task 3-2 8 | * [ ] task 3-3 9 | - [ ] task 4 10 | -------------------------------------------------------------------------------- /content/tex.md: -------------------------------------------------------------------------------- 1 | ## TeX blocks 2 | 3 | ### Super formula 1 4 | ```tex 5 | E=mc^2 6 | ``` 7 | ### Super formula 2 8 | ```tex 9 | \sin(\alpha)^{\theta}=\sum_{i=0}^{n}(x^i + \cos(f)) 10 | ``` 11 | ### Super formula 3 12 | ```tex 13 | \frac{1}{\Bigl(\sqrt{\phi \sqrt{5}}-\phi\Bigr) e^{\frac25 \pi}} = 1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}} {1+\frac{e^{-8\pi}} {1+\cdots} } } } 14 | ``` 15 | -------------------------------------------------------------------------------- /content/video.md: -------------------------------------------------------------------------------- 1 | ### YouTube 2 | 3 | http://www.youtube.com/embed/QILiHiTD3uc 4 | 5 | ### Vimeo 6 | 7 | http://vimeo.com/66140585 8 | 9 | ### Daylymotion 10 | 11 | http://dailymotion.com/video/xsr67x 12 | 13 | ### Vine 14 | 15 | https://vine.co/v/MjpK9pmKvzu 16 | 17 | ## TED 18 | 19 | http://www.ted.com/talks/pico_iyer_where_is_home 20 | http://www.ted.com/playlists/323/the_influence_of_algorithms 21 | -------------------------------------------------------------------------------- /demo/app.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Navbar, Nav, NavItem } from 'react-bootstrap'; 3 | import { Markdown, MarkdownEditor } from '../src'; 4 | import Sidebar, { contentLinks } from './sidebar'; 5 | import style from './style.scss'; 6 | 7 | export default class App extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | content: '', 12 | activeKey: -1, 13 | mode: 'preview', 14 | }; 15 | this.select = this.select.bind(this); 16 | this.select(contentLinks[0], 0); 17 | } 18 | 19 | select(item, i) { 20 | fetch(item.url) 21 | .then(response => response.text()) 22 | .then((content) => { 23 | this.setState({ content, activeKey: i }); 24 | }); 25 | } 26 | 27 | renderContent() { 28 | if (this.state.mode === 'source') { 29 | const props = { 30 | value: this.state.content, 31 | style: { 32 | height: '600px', 33 | }, 34 | }; 35 | return ; 36 | } 37 | return ; 38 | } 39 | 40 | render() { 41 | const toggleMode = () => { 42 | const mode = this.state.mode === 'source' ? 'preview' : 'source'; 43 | this.setState({ mode }); 44 | }; 45 | return ( 46 |
47 | 48 |
49 | 50 | 55 | 56 | {this.renderContent()} 57 |
58 |
59 |
60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /demo/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import App from './app'; 4 | 5 | render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /demo/sidebar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Nav, NavItem } from 'react-bootstrap'; 3 | import Sidebar from 'react-sidebar'; 4 | 5 | export const contentLinks = [ 6 | { 7 | url: './content/all.md', 8 | label: 'All in one', 9 | }, 10 | { 11 | url: './content/code.md', 12 | label: 'Code blocks', 13 | }, 14 | { 15 | url: './content/diff.md', 16 | label: 'Diff blocks', 17 | }, 18 | { 19 | url: './content/tex.md', 20 | label: 'TeX blocks', 21 | }, 22 | { 23 | url: './content/sequence.md', 24 | label: 'Sequence diagrams', 25 | }, 26 | { 27 | url: './content/flow.md', 28 | label: 'Flow charts', 29 | }, 30 | { 31 | url: './content/railroad.md', 32 | label: 'Railroad diagrams', 33 | }, 34 | { 35 | url: './content/image.md', 36 | label: 'Embed image', 37 | }, 38 | { 39 | url: './content/audio.md', 40 | label: 'Embed audio', 41 | }, 42 | { 43 | url: './content/video.md', 44 | label: 'Embed video', 45 | }, 46 | { 47 | url: './content/snippets.md', 48 | label: 'Online snippets', 49 | }, 50 | { 51 | url: './content/maps.md', 52 | label: 'Maps', 53 | }, 54 | { 55 | url: './content/tasklist.md', 56 | label: 'Task lists', 57 | }, 58 | ]; 59 | 60 | function menuItems() { 61 | return contentLinks.map((t, i) => { 62 | const linkProps = { 63 | key: i, 64 | href: t.url, 65 | eventKey: i, 66 | style: { 67 | margin: '4px', 68 | }, 69 | }; 70 | return {t.label}; 71 | }); 72 | } 73 | 74 | export default function AppSidebar(props) { 75 | const onSelect = i => props.onSelect(contentLinks[i], i); 76 | const content = ( 77 | 80 | ); 81 | return ( 82 | 83 | {props.children} 84 | 85 | ); 86 | } 87 | -------------------------------------------------------------------------------- /demo/style.scss: -------------------------------------------------------------------------------- 1 | .content_container { 2 | margin: 16px; 3 | } 4 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e # exit with nonzero exit code if anything fails 3 | 4 | # clear and re-create the out directory 5 | rm -rf site || exit 0; 6 | mkdir site; 7 | 8 | # build site 9 | bash makesite.sh 10 | 11 | # go to the site directory and create a *new* git repo 12 | cd site 13 | 14 | # fix absolute links 15 | OLD='/static/' 16 | NEW='./static/' 17 | sed -i 's/$OLD/$NEW/g' index.html 18 | 19 | git init 20 | 21 | # inside this git repo we'll pretend to be a new user 22 | git config user.name "Travis CI" 23 | git config user.email "stodyshev@gmail.com" 24 | 25 | # The first and only commit to this new git repo contains all the 26 | # files present with the commit message "Deploy to GitHub Pages". 27 | git add . 28 | git commit -m "Deploy to GitHub Pages" 29 | 30 | # Force push from the current repo's master branch to the remote 31 | # repo's gh-pages branch. (All previous history on the gh-pages branch 32 | # will be lost, since we are overwriting it.) We redirect any output to 33 | # /dev/null to hide any sensitive credential data that might otherwise be exposed. 34 | git push --force --quiet "https://${GH_TOKEN}@${GH_REF}" master:gh-pages > /dev/null 2>&1 35 | -------------------------------------------------------------------------------- /devserver.js: -------------------------------------------------------------------------------- 1 | const startServer = require('react-devpack').startServer; 2 | 3 | startServer(); 4 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Demo page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /lib/codeblock.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.render = render; 7 | exports.default = plugin; 8 | 9 | var _react = require('react'); 10 | 11 | var _react2 = _interopRequireDefault(_react); 12 | 13 | var _server = require('react-dom/server'); 14 | 15 | var _prismjsPackage = require('prismjs-package'); 16 | 17 | var _prismjsPackage2 = _interopRequireDefault(_prismjsPackage); 18 | 19 | var _prism = require('prismjs-package/themes/prism.css'); 20 | 21 | var _prism2 = _interopRequireDefault(_prism); 22 | 23 | var _reactDiffview = require('react-diffview'); 24 | 25 | var _reactDiffview2 = _interopRequireDefault(_reactDiffview); 26 | 27 | var _utils = require('markdown-it/lib/common/utils'); 28 | 29 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 30 | 31 | // eslint-disable-line 32 | var externalLangs = { 33 | seq: 'sequence', 34 | sequence: 'sequence', 35 | // TODO flow could be ambigious with flow.js 36 | flow: 'flowchart', 37 | flowchart: 'flowchart', 38 | railroad: 'railroad', 39 | tex: 'tex' 40 | }; 41 | // TODO do that in prismjs-package 42 | 43 | 44 | var externalClass = { 45 | tex: 'tex' 46 | }; 47 | 48 | function external(code, lang) { 49 | var className = externalClass[lang] || 'diagram'; 50 | return '
' + code + '
'; 51 | } 52 | 53 | function render(code, language) { 54 | var lang = (language || '').toLowerCase(); 55 | var externalLang = externalLangs[lang]; 56 | if (externalLang) { 57 | return external(code, externalLang); 58 | } 59 | if (lang.match(/^diff?/i)) { 60 | var view = _react2.default.createElement(_reactDiffview2.default, { source: code }); 61 | return (0, _server.renderToString)(view); 62 | } 63 | return (0, _prismjsPackage2.default)(code, language); 64 | } 65 | 66 | /* eslint-disable no-param-reassign */ 67 | function plugin(md) { 68 | md.renderer.rules.fence = function (tokens, idx, options) { 69 | var token = tokens[idx]; 70 | var info = token.info ? (0, _utils.unescapeAll)(token.info).trim() : ''; 71 | var langName = ''; 72 | 73 | if (info) { 74 | langName = info.split(/\s+/g)[0]; 75 | token.attrJoin('class', options.langPrefix + langName); 76 | } 77 | 78 | return render(token.content, langName); 79 | }; 80 | } 81 | /* eslint-enable */ -------------------------------------------------------------------------------- /lib/content.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _react = require('react'); 10 | 11 | var _react2 = _interopRequireDefault(_react); 12 | 13 | var _reactDom = require('react-dom'); 14 | 15 | var _reactDom2 = _interopRequireDefault(_reactDom); 16 | 17 | var _stripIndent = require('strip-indent'); 18 | 19 | var _stripIndent2 = _interopRequireDefault(_stripIndent); 20 | 21 | var _githubMarkdown = require('github-markdown-css/github-markdown.css'); 22 | 23 | var _githubMarkdown2 = _interopRequireDefault(_githubMarkdown); 24 | 25 | var _render2 = require('./render'); 26 | 27 | var _render3 = _interopRequireDefault(_render2); 28 | 29 | var _inject = require('./inject'); 30 | 31 | var _inject2 = _interopRequireDefault(_inject); 32 | 33 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 34 | 35 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 36 | 37 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 38 | 39 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 40 | 41 | var Content = function (_Component) { 42 | _inherits(Content, _Component); 43 | 44 | function Content() { 45 | _classCallCheck(this, Content); 46 | 47 | return _possibleConstructorReturn(this, (Content.__proto__ || Object.getPrototypeOf(Content)).apply(this, arguments)); 48 | } 49 | 50 | _createClass(Content, [{ 51 | key: 'componentDidMount', 52 | value: function componentDidMount() { 53 | this.replaceBlocks(); 54 | } 55 | }, { 56 | key: 'componentDidUpdate', 57 | value: function componentDidUpdate() { 58 | this.replaceBlocks(); 59 | } 60 | }, { 61 | key: 'replaceBlocks', 62 | value: function replaceBlocks() { 63 | var root = $(_reactDom2.default.findDOMNode(this)); // eslint-disable-line 64 | (0, _inject2.default)(root.find('.injection')); 65 | } 66 | }, { 67 | key: 'render', 68 | value: function render() { 69 | var source = this.props.source; 70 | 71 | var html = (0, _render3.default)((0, _stripIndent2.default)(source)); 72 | var className = _githubMarkdown2.default['markdown-body']; 73 | return _react2.default.createElement('span', { className: className, dangerouslySetInnerHTML: { __html: html } }); // eslint-disable-line 74 | } 75 | }]); 76 | 77 | return Content; 78 | }(_react.Component); 79 | 80 | exports.default = Content; -------------------------------------------------------------------------------- /lib/editor/editor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.MarkdownEditor = undefined; 7 | 8 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 9 | 10 | var _lodash = require('lodash'); 11 | 12 | var _lodash2 = _interopRequireDefault(_lodash); 13 | 14 | var _react = require('react'); 15 | 16 | var _react2 = _interopRequireDefault(_react); 17 | 18 | var _reactSplitPane = require('react-split-pane'); 19 | 20 | var _reactSplitPane2 = _interopRequireDefault(_reactSplitPane); 21 | 22 | var _reactAce = require('react-ace'); 23 | 24 | var _reactAce2 = _interopRequireDefault(_reactAce); 25 | 26 | require('brace/mode/markdown'); 27 | 28 | require('brace/theme/github'); 29 | 30 | var _markdown = require('../markdown'); 31 | 32 | var _markdown2 = _interopRequireDefault(_markdown); 33 | 34 | var _toolbar = require('./toolbar'); 35 | 36 | var _toolbar2 = _interopRequireDefault(_toolbar); 37 | 38 | var _style = require('./style.scss'); 39 | 40 | var _style2 = _interopRequireDefault(_style); 41 | 42 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 43 | 44 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 45 | 46 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 47 | 48 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 49 | 50 | // TODO customizable tabs e.g. localization 51 | // TODO textarea mode without rich editor 52 | // TODO fix split mode 53 | // TODO sync scrollbars for editor and viewer 54 | 55 | var MarkdownEditor = exports.MarkdownEditor = function (_Component) { 56 | _inherits(MarkdownEditor, _Component); 57 | 58 | function MarkdownEditor(props) { 59 | _classCallCheck(this, MarkdownEditor); 60 | 61 | var _this = _possibleConstructorReturn(this, (MarkdownEditor.__proto__ || Object.getPrototypeOf(MarkdownEditor)).call(this, props)); 62 | 63 | _this.state = { 64 | mode: 'edit', 65 | value: props.value || '' 66 | }; 67 | return _this; 68 | } 69 | 70 | _createClass(MarkdownEditor, [{ 71 | key: 'componentWillReceiveProps', 72 | value: function componentWillReceiveProps(nextProps) { 73 | this.setState({ value: nextProps.value }); 74 | } 75 | }, { 76 | key: 'renderAce', 77 | value: function renderAce() { 78 | var _this2 = this; 79 | 80 | var _ref = this.props.style || {}, 81 | width = _ref.width, 82 | height = _ref.height; 83 | 84 | var onChange = function onChange(value) { 85 | (_this2.props.onChange || _lodash2.default.noop)(value); 86 | _this2.setState({ value: value }); 87 | }; 88 | var aceProps = { 89 | mode: 'markdown', 90 | theme: 'github', 91 | value: this.state.value, 92 | onChange: onChange, 93 | width: width || '100%', 94 | height: height || '100%' 95 | }; 96 | return _react2.default.createElement(_reactAce2.default, aceProps); 97 | } 98 | }, { 99 | key: 'renderNormalView', 100 | value: function renderNormalView() { 101 | if (this.state.mode === 'edit') { 102 | return this.renderAce(); 103 | } 104 | return _react2.default.createElement(_markdown2.default, { source: this.state.value }); 105 | } 106 | }, { 107 | key: 'renderSplitView', 108 | value: function renderSplitView() { 109 | return _react2.default.createElement( 110 | _reactSplitPane2.default, 111 | { split: 'vertical' }, 112 | this.renderAce(), 113 | _react2.default.createElement(_markdown2.default, { source: this.state.value }) 114 | ); 115 | } 116 | }, { 117 | key: 'render', 118 | value: function render() { 119 | var _this3 = this; 120 | 121 | var mode = this.state.mode; 122 | var _props = this.props, 123 | style = _props.style, 124 | splitView = _props.splitView; 125 | 126 | var onAction = function onAction(type) { 127 | // TODO implement formatting actions 128 | switch (type) { 129 | case 'mode': 130 | _this3.setState({ mode: mode === 'edit' ? 'preview' : 'edit' }); 131 | break; 132 | case 'save': 133 | (_this3.props.onSave || _lodash2.default.noop)(_this3.state.value); 134 | break; 135 | default: 136 | console.log('unhandled action ' + type); 137 | break; 138 | } 139 | }; 140 | return _react2.default.createElement( 141 | 'div', 142 | { className: _style2.default.markdown_editor, style: style }, 143 | _react2.default.createElement(_toolbar2.default, { mode: mode, onAction: onAction }), 144 | splitView ? this.renderSplitView() : this.renderNormalView() 145 | ); 146 | } 147 | }]); 148 | 149 | return MarkdownEditor; 150 | }(_react.Component); 151 | 152 | MarkdownEditor.propTypes = { 153 | value: _react.PropTypes.string, 154 | onSave: _react.PropTypes.func, 155 | onChange: _react.PropTypes.func 156 | }; 157 | MarkdownEditor.defaultProps = { 158 | value: '', 159 | onSave: _lodash2.default.noop, 160 | onChange: _lodash2.default.noop 161 | }; 162 | exports.default = MarkdownEditor; -------------------------------------------------------------------------------- /lib/editor/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _editor = require('./editor'); 8 | 9 | Object.defineProperty(exports, 'default', { 10 | enumerable: true, 11 | get: function get() { 12 | return _interopRequireDefault(_editor).default; 13 | } 14 | }); 15 | 16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -------------------------------------------------------------------------------- /lib/editor/split.scss: -------------------------------------------------------------------------------- 1 | .Resizer { 2 | background: #000; 3 | opacity: .2; 4 | z-index: 1; 5 | box-sizing: border-box; 6 | background-clip: padding-box; 7 | } 8 | 9 | .Resizer:hover { 10 | transition: all 2s ease; 11 | } 12 | 13 | .Resizer.horizontal { 14 | height: 11px; 15 | margin: -5px 0; 16 | border-top: 5px solid rgba(255, 255, 255, 0); 17 | border-bottom: 5px solid rgba(255, 255, 255, 0); 18 | cursor: row-resize; 19 | width: 100%; 20 | } 21 | 22 | .Resizer.horizontal:hover { 23 | border-top: 5px solid rgba(0, 0, 0, 0.5); 24 | border-bottom: 5px solid rgba(0, 0, 0, 0.5); 25 | } 26 | 27 | .Resizer.vertical { 28 | width: 11px; 29 | margin: 0 -5px; 30 | border-left: 5px solid rgba(255, 255, 255, 0); 31 | border-right: 5px solid rgba(255, 255, 255, 0); 32 | cursor: col-resize; 33 | height: 100%; 34 | } 35 | 36 | .Resizer.vertical:hover { 37 | border-left: 5px solid rgba(0, 0, 0, 0.5); 38 | border-right: 5px solid rgba(0, 0, 0, 0.5); 39 | } 40 | -------------------------------------------------------------------------------- /lib/editor/style.scss: -------------------------------------------------------------------------------- 1 | @import "split"; 2 | 3 | .markdown_editor { 4 | min-height: 300px; 5 | } 6 | 7 | .markdown_editor .toolbar span { 8 | cursor: pointer; 9 | display: inline-block; 10 | margin: 2px; 11 | padding: 4px; 12 | min-width: 20px; 13 | min-height: 20px; 14 | text-align: center; 15 | vertical-align: middle; 16 | border-radius: 25%; 17 | } 18 | 19 | .markdown_editor .toolbar span:hover { 20 | background-color: lightgray; 21 | } 22 | -------------------------------------------------------------------------------- /lib/editor/toolbar.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | exports.default = Toolbar; 10 | 11 | var _lodash = require('lodash'); 12 | 13 | var _lodash2 = _interopRequireDefault(_lodash); 14 | 15 | var _react = require('react'); 16 | 17 | var _react2 = _interopRequireDefault(_react); 18 | 19 | var _classnames = require('classnames'); 20 | 21 | var _classnames2 = _interopRequireDefault(_classnames); 22 | 23 | var _cssEffects = require('css-effects'); 24 | 25 | var _style = require('./style.scss'); 26 | 27 | var _style2 = _interopRequireDefault(_style); 28 | 29 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 30 | 31 | function Button(props) { 32 | var className = (0, _classnames2.default)(props.icon ? props.icon : null); 33 | var attrs = _lodash2.default.pick(props); 34 | var btnClass = ''; 35 | var btnAttrs = _lodash2.default.pick(props, 'onClick'); 36 | if (props.title) { 37 | btnClass = (0, _cssEffects.hint)(); 38 | btnAttrs['data-hint'] = props.title; 39 | } 40 | return _react2.default.createElement( 41 | 'span', 42 | _extends({ className: btnClass }, btnAttrs), 43 | _react2.default.createElement( 44 | 'i', 45 | _extends({ className: className }, attrs), 46 | props.text 47 | ) 48 | ); 49 | } 50 | 51 | function Toolbar(props) { 52 | var action = function action(type) { 53 | return function () { 54 | return props.onAction(type); 55 | }; 56 | }; 57 | var editMode = props.mode === 'edit'; 58 | var modeButton = { 59 | icon: editMode ? 'fa fa-eye' : 'fa fa-edit', 60 | title: editMode ? 'Preview' : 'Edit', 61 | onClick: action('mode') 62 | }; 63 | // TODO disabled button states 64 | return _react2.default.createElement( 65 | 'div', 66 | { className: _style2.default.toolbar }, 67 | _react2.default.createElement(Button, modeButton), 68 | _react2.default.createElement(Button, { icon: 'fa fa-floppy-o', title: 'Save', onClick: action('save') }), 69 | _react2.default.createElement(Button, { icon: 'fa fa-undo', title: 'Undo', onClick: action('undo') }), 70 | _react2.default.createElement(Button, { icon: 'fa fa-repeat', title: 'Redo', onClick: action('redo') }), 71 | _react2.default.createElement(Button, { icon: 'fa fa-bold', title: 'Bold', onClick: action('bold') }), 72 | _react2.default.createElement(Button, { icon: 'fa fa-italic', title: 'Italic', onClick: action('italic') }), 73 | _react2.default.createElement(Button, { icon: 'fa fa-list-ol', title: 'Numbered list', onClick: action('ol') }), 74 | _react2.default.createElement(Button, { icon: 'fa fa-list', title: 'Bulleted list', onClick: action('ul') }) 75 | ); 76 | } -------------------------------------------------------------------------------- /lib/embed.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | exports.default = plugin; 10 | 11 | var _lodash = require('lodash'); 12 | 13 | var _lodash2 = _interopRequireDefault(_lodash); 14 | 15 | var _react = require('react'); 16 | 17 | var _react2 = _interopRequireDefault(_react); 18 | 19 | var _server = require('react-dom/server'); 20 | 21 | var _classnames = require('classnames'); 22 | 23 | var _classnames2 = _interopRequireDefault(_classnames); 24 | 25 | var _hashtrie = require('hashtrie'); 26 | 27 | var _hashtrie2 = _interopRequireDefault(_hashtrie); 28 | 29 | var _regex = require('./regex'); 30 | 31 | var regex = _interopRequireWildcard(_regex); 32 | 33 | var _style = require('./style.scss'); 34 | 35 | var _style2 = _interopRequireDefault(_style); 36 | 37 | var _parseuri = require('./parseuri'); 38 | 39 | var _parseuri2 = _interopRequireDefault(_parseuri); 40 | 41 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 42 | 43 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 44 | 45 | var rulemap = _hashtrie2.default.empty; 46 | 47 | function queryString(params) { 48 | return _lodash2.default.map(params, function (v, k) { 49 | return k + '=' + v; 50 | }).join('&'); 51 | } 52 | 53 | // embedding with embed.js 54 | function embed(url) { 55 | return '
' + url + '
'; 56 | } 57 | 58 | function iframe(props) { 59 | var containerProps = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 60 | 61 | var iframeProps = _extends({}, props, { 62 | frameBorder: 0, 63 | webkitAllowFullScreen: true, 64 | mozallowfullscreen: true, 65 | allowFullScreen: true 66 | }); 67 | delete iframeProps.className; 68 | delete iframeProps.afterFrame; 69 | var className = (0, _classnames2.default)(_style2.default.embed_container, props.className); 70 | var container = _react2.default.createElement( 71 | 'div', 72 | _extends({}, containerProps, { className: className }), 73 | _react2.default.createElement('iframe', iframeProps), 74 | props.afterFrame || null 75 | ); 76 | return (0, _server.renderToStaticMarkup)(container); 77 | } 78 | 79 | function canApplyRule(test, url) { 80 | if (typeof test === 'function') { 81 | return test(url); 82 | } 83 | return test.exec(url); 84 | } 85 | 86 | function makeRule(test, render) { 87 | if (test === undefined) { 88 | throw new Error('nre'); 89 | } 90 | if (_lodash2.default.isArray(test)) { 91 | var fn = function fn(url) { 92 | return _lodash2.default.some(test, function (t) { 93 | return canApplyRule(t, url); 94 | }); 95 | }; 96 | return { test: fn, render: render }; 97 | } 98 | return { test: test, render: render }; 99 | } 100 | 101 | function rule(host, test, render) { 102 | var value = makeRule(test, render); 103 | if (host) { 104 | rulemap = rulemap.set(host, value); 105 | rulemap = rulemap.set('www.' + host, value); 106 | } 107 | return value; 108 | } 109 | 110 | function rules(host, set) { 111 | // eslint-disable-line 112 | rulemap = rulemap.set(host, set); 113 | rulemap = rulemap.set('www.' + host, set); 114 | } 115 | 116 | var image = rule('', regex.imageExt, function (url) { 117 | return ''; 118 | }); 119 | 120 | // TODO support watch URLs 121 | rule('youtube.com', regex.youtube, function (url) { 122 | return iframe({ className: _style2.default.youtube, src: url }); 123 | }); 124 | 125 | rule('vimeo.com', regex.vimeo, function (url, match) { 126 | var src = 'http://player.vimeo.com/video/' + match[1]; 127 | return iframe({ className: _style2.default.vimeo, src: src }); 128 | }); 129 | 130 | rule('dailymotion.com', regex.dailymotion, function (url, match) { 131 | var src = 'http://www.dailymotion.com/embed/video/' + match[1]; 132 | return iframe({ className: _style2.default.daylymotion, src: src }); 133 | }); 134 | 135 | var vineScript = ''; // eslint-disable-line 136 | 137 | rule('vine.co', regex.vine, function (url, match) { 138 | var src = 'https://vine.co/v/' + match[1] + '/embed/simple'; 139 | return iframe({ className: _style2.default.vine, src: src, afterFrame: vineScript }); 140 | }); 141 | 142 | rule('liveleak.com', regex.liveleak, embed); 143 | 144 | rule('ted.com', regex.ted, function (url, match) { 145 | var src = 'https://embed-ssl.ted.com/' + match[1]; 146 | return iframe({ className: _style2.default.ted, src: src }); 147 | }); 148 | 149 | rule('ustream.tv', regex.ustream, embed); 150 | 151 | rule('google.com', regex.googleMap, function (url, match) { 152 | var src = 'https://www.google.com/maps/embed?' + match[1]; 153 | return iframe({ className: _style2.default.google_map, src: src }); 154 | }); 155 | 156 | // image 157 | rule('instagram.com', regex.instagram, function (url, match) { 158 | var src = '//instagram.com/p/' + match[1] + '/embed/'; 159 | return iframe({ className: _style2.default.instagram, src: src, scrolling: 'no' }); 160 | }); 161 | 162 | rule('flickr.com', regex.flickr, embed); 163 | // const src = `https://www.slideshare.net/slideshow/embed_code/key/zTckhjEe9nT5j8`; 164 | rule('slideshare.net', regex.slideshare, embed); 165 | 166 | // rule('imgur.com', regex.imgur, (url, match) => { 167 | //
168 | // How to tell when your cat is fully charged 169 | //
170 | // 171 | // }); 172 | 173 | // audio 174 | 175 | var soundcloudParams = queryString({ 176 | auto_play: 'false', 177 | hide_related: 'false', 178 | show_comments: 'true', 179 | show_user: 'true', 180 | show_reposts: 'false', 181 | visual: 'false', 182 | download: 'false', 183 | color: 'f50000', 184 | theme_color: 'f50000' 185 | }); 186 | 187 | rule('soundcloud.com', regex.soundcloud, function (url, match) { 188 | var player = 'https://w.soundcloud.com/player/?url=soundcloud.com/' + match[1] + '/' + match[2] + '&'; 189 | var src = '' + player + soundcloudParams; 190 | return iframe({ className: _style2.default.soundcloud, src: src, scrolling: 'no' }); 191 | }); 192 | 193 | rule('spotify.com', regex.spotify, embed); 194 | // code 195 | rule('github.com', regex.github, embed); 196 | rule('gist.github.com', regex.gist, embed); 197 | 198 | rule('codepen.io', regex.codepen, function (url, match) { 199 | var slug = match[2]; 200 | var src = 'https://codepen.io/' + match[1] + '/embed/preview/' + slug + '?height=300&slug-hash=' + slug + '&default-tab=result&host=http%3A%2F%2Fcodepen.io'; // eslint-disable-line 201 | return iframe({ className: _style2.default.codepen, src: src }); 202 | }); 203 | 204 | rule('ideone.com', regex.ideone, embed); 205 | rule('jsbin.com', regex.jsbin, embed); 206 | rule('jsfiddle.net', regex.jsfiddle, embed); 207 | rule('plnkr.co', regex.plunker, embed); 208 | // social 209 | rule('twitter.com', regex.twitter, embed); 210 | 211 | function applyRule(self, url) { 212 | if (typeof self.test === 'function') { 213 | if (self.test(url)) { 214 | return self.render(url); 215 | } 216 | return undefined; 217 | } 218 | var match = self.test.exec(url); 219 | if (match) { 220 | return self.render(url, match); 221 | } 222 | return undefined; 223 | } 224 | 225 | function embedURL(url) { 226 | if (!url) return undefined; 227 | 228 | var uri = (0, _parseuri2.default)(url); 229 | if (!uri) return undefined; 230 | 231 | if (url.match(image.test)) { 232 | return image.render(url); 233 | } 234 | 235 | var set = rulemap.get(uri.host); 236 | if (!set) return undefined; 237 | 238 | if (_lodash2.default.isArray(set)) { 239 | for (var i = 0; i < set.length; i += 1) { 240 | var result = applyRule(set[i], url); 241 | if (result) return result; 242 | } 243 | return undefined; 244 | } 245 | 246 | return applyRule(set, url); 247 | } 248 | 249 | /* eslint-disable no-param-reassign */ 250 | function plugin(md) { 251 | var skip = false; 252 | md.renderer.rules.link_open = function link(tokens, i, options) { 253 | if (i + 2 < tokens.length && tokens[i + 1].type === 'text' && tokens[i + 2].type === 'link_close') { 254 | var result = embedURL(tokens[i + 1].content); 255 | if (typeof result === 'string') { 256 | skip = true; 257 | return result; 258 | } 259 | } 260 | return md.renderer.renderToken(tokens, i, options); 261 | }; 262 | 263 | var renderText = md.renderer.rules.text; 264 | md.renderer.rules.text = function link(tokens, i, options, self) { 265 | if (skip) return ''; 266 | return renderText(tokens, i, options, self); 267 | }; 268 | 269 | md.renderer.rules.link_close = function link(tokens, i, options) { 270 | if (skip) { 271 | skip = false; 272 | return ''; 273 | } 274 | return md.renderer.renderToken(tokens, i, options); 275 | }; 276 | } 277 | /* eslint-enable */ -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _markdown = require('./markdown'); 8 | 9 | Object.defineProperty(exports, 'Markdown', { 10 | enumerable: true, 11 | get: function get() { 12 | return _interopRequireDefault(_markdown).default; 13 | } 14 | }); 15 | Object.defineProperty(exports, 'default', { 16 | enumerable: true, 17 | get: function get() { 18 | return _interopRequireDefault(_markdown).default; 19 | } 20 | }); 21 | 22 | var _editor = require('./editor'); 23 | 24 | Object.defineProperty(exports, 'MarkdownEditor', { 25 | enumerable: true, 26 | get: function get() { 27 | return _interopRequireDefault(_editor).default; 28 | } 29 | }); 30 | 31 | var _render = require('./render'); 32 | 33 | Object.defineProperty(exports, 'render', { 34 | enumerable: true, 35 | get: function get() { 36 | return _interopRequireDefault(_render).default; 37 | } 38 | }); 39 | 40 | var _linkify = require('./linkify'); 41 | 42 | Object.defineProperty(exports, 'linkTo', { 43 | enumerable: true, 44 | get: function get() { 45 | return _linkify.linkTo; 46 | } 47 | }); 48 | 49 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -------------------------------------------------------------------------------- /lib/inject.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = render; 7 | 8 | var _jsSequenceDiagrams = require('js-sequence-diagrams'); 9 | 10 | var _jsSequenceDiagrams2 = _interopRequireDefault(_jsSequenceDiagrams); 11 | 12 | var _flowchart = require('flowchart.js'); 13 | 14 | var _flowchart2 = _interopRequireDefault(_flowchart); 15 | 16 | var _katex = require('katex'); 17 | 18 | var _katex2 = _interopRequireDefault(_katex); 19 | 20 | var _railroad = require('./railroad'); 21 | 22 | var _railroad2 = _interopRequireDefault(_railroad); 23 | 24 | var _embed = require('embed-js/src/embed.js'); 25 | 26 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 27 | 28 | // eslint-disable-line 29 | // import embedStyles from 'embed-js/src/embed.css'; // eslint-disable-line 30 | 31 | function inject(element) { 32 | var $e = $(element); 33 | if ($e.is('.diagram')) { 34 | var lang = $e.data('lang'); 35 | switch (lang) { 36 | case 'sequence': 37 | $e.sequenceDiagram({ theme: 'hand' }); 38 | break; 39 | case 'flowchart': 40 | $e.flowChart(); 41 | break; 42 | case 'railroad': 43 | (0, _railroad2.default)(element); 44 | break; 45 | default: 46 | break; 47 | } 48 | return; 49 | } 50 | if ($e.is('.tex')) { 51 | try { 52 | var text = $e.text(); 53 | _katex2.default.render(text, element); 54 | } catch (e) { 55 | console.log('katex failed:', e); 56 | } 57 | return; 58 | } 59 | if ($e.is('.embedjs')) { 60 | var em = new window.EmbedJS({ 61 | element: element 62 | }); 63 | em.render(); 64 | } 65 | } 66 | // import katexStyles from '../bower_components/KaTeX/dist/katex.min.css'; // eslint-disable-line 67 | 68 | /*eslint-enable*/ 69 | /*eslint-disable*/ 70 | function render(elems) { 71 | $.each(elems, function (i, e) { 72 | return inject(e); 73 | }); 74 | } -------------------------------------------------------------------------------- /lib/linkify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = configureLinkifier; 7 | var linkTo = { 8 | user: 'https://twitter.com/', 9 | issue: 'https://github.com/reactbits/markdown/issues/', 10 | hashtag: 'https://twitter.com/hashtag/' 11 | }; 12 | 13 | exports.linkTo = linkTo; 14 | function configureLinkifier(linkify) { 15 | linkify.set({ 16 | fuzzyLink: true, 17 | fuzzyIP: false, 18 | fuzzyEmail: true 19 | }); 20 | 21 | var rexprs = {}; 22 | 23 | function register(prefix, key, replaceUrl) { 24 | linkify.add(prefix, { 25 | validate: function validate(text, pos, self) { 26 | var tail = text.slice(pos); 27 | 28 | var re = rexprs[key]; 29 | if (!re) { 30 | re = new RegExp('^([a-zA-Z0-9_]){1,15}(?!_)(?=$|' + self.re.src_ZPCc + ')'); 31 | rexprs[key] = re; 32 | } 33 | 34 | if (re.test(tail)) { 35 | // Linkifier allows punctuation chars before prefix, 36 | // but we additionally disable `@` ("@@mention" is invalid) 37 | if (pos >= 2 && tail[pos - 2] === prefix) { 38 | return false; 39 | } 40 | return tail.match(re)[0].length; 41 | } 42 | 43 | return 0; 44 | }, 45 | 46 | /*eslint-disable*/ 47 | normalize: function normalize(match) { 48 | match.url = replaceUrl(match.url); 49 | } 50 | }); 51 | } 52 | 53 | register('@', 'mention', function (url) { 54 | return linkTo.user + url.replace(/^@/, ''); 55 | }); 56 | register('issue ', 'issue', function (url) { 57 | return linkTo.issue + url.replace(/^issue /, ''); 58 | }); 59 | register('#', 'hashtag', function (url) { 60 | return linkTo.hashtag + url.replace(/^#/, ''); 61 | }); 62 | } -------------------------------------------------------------------------------- /lib/markdown.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _react = require('react'); 10 | 11 | var _react2 = _interopRequireDefault(_react); 12 | 13 | var _content = require('./content'); 14 | 15 | var _content2 = _interopRequireDefault(_content); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 20 | 21 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 22 | 23 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 24 | 25 | var Markdown = function (_Component) { 26 | _inherits(Markdown, _Component); 27 | 28 | function Markdown() { 29 | _classCallCheck(this, Markdown); 30 | 31 | return _possibleConstructorReturn(this, (Markdown.__proto__ || Object.getPrototypeOf(Markdown)).apply(this, arguments)); 32 | } 33 | 34 | _createClass(Markdown, [{ 35 | key: 'render', 36 | value: function render() { 37 | var source = this.props.source; 38 | 39 | var className = this.props.className || 'markdown'; 40 | var style = this.props.style || {}; 41 | var content = void 0; 42 | if (source) { 43 | content = _react2.default.createElement(_content2.default, { source: source }); 44 | } else { 45 | content = _react2.default.Children.map(this.props.children, function (child, i) { 46 | if (typeof child === 'string') { 47 | return _react2.default.createElement(_content2.default, { key: i, source: child }); 48 | } 49 | return child; 50 | }); 51 | } 52 | return _react2.default.createElement( 53 | 'div', 54 | { className: className, style: style }, 55 | content 56 | ); 57 | } 58 | }]); 59 | 60 | return Markdown; 61 | }(_react.Component); 62 | 63 | exports.default = Markdown; -------------------------------------------------------------------------------- /lib/parseuri.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = parseuri; 7 | /** 8 | * Parses an URI 9 | * 10 | * @author Steven Levithan (MIT license) 11 | * @api private 12 | */ 13 | 14 | /* eslint-disable */ 15 | var regex = exports.regex = /^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; 16 | /* eslint-enable */ 17 | 18 | var parts = ['source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor']; 19 | 20 | function parseuri(str) { 21 | var src = str; 22 | var b = str.indexOf('['); 23 | var e = str.indexOf(']'); 24 | 25 | if (b !== -1 && e !== -1) { 26 | str = str.substring(0, b) + str.substring(b, e).replace(/:/g, ';') + str.substring(e, str.length); // eslint-disable-line 27 | } 28 | 29 | var m = regex.exec(str || ''); 30 | if (!m) { 31 | return null; 32 | } 33 | 34 | var uri = {}; 35 | var i = 14; 36 | 37 | while (i--) { 38 | // eslint-disable-line 39 | uri[parts[i]] = m[i] || ''; 40 | } 41 | 42 | if (b !== -1 && e !== -1) { 43 | uri.source = src; 44 | uri.host = uri.host.substring(1, uri.host.length - 1).replace(/;/g, ':'); 45 | uri.authority = uri.authority.replace('[', '').replace(']', '').replace(/;/g, ':'); 46 | uri.ipv6uri = true; 47 | } 48 | 49 | return uri; 50 | } -------------------------------------------------------------------------------- /lib/railroad.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = render; 7 | 8 | var _railroadDiagrams = require('railroad-diagrams'); 9 | 10 | var railroad = _interopRequireWildcard(_railroadDiagrams); 11 | 12 | var _railroadDiagrams2 = require('railroad-diagrams/railroad-diagrams.css'); 13 | 14 | var _railroadDiagrams3 = _interopRequireDefault(_railroadDiagrams2); 15 | 16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 17 | 18 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 19 | 20 | /* eslint-disable */ 21 | function render(elem) { 22 | var $e = $(elem); 23 | try { 24 | var diagramSource = $e.text(); 25 | 26 | // context for eval 27 | /* eslint-disable */ 28 | var Diagram = railroad.Diagram; 29 | var ComplexDiagram = railroad.ComplexDiagram; 30 | var Sequence = railroad.Sequence; 31 | var Stack = railroad.Stack; 32 | var OptionalSequence = railroad.OptionalSequence; 33 | var Choice = railroad.Choice; 34 | var MultipleChoice = railroad.MultipleChoice; 35 | var Optional = railroad.Optional; 36 | var OneOrMore = railroad.OneOrMore; 37 | var ZeroOrMore = railroad.ZeroOrMore; 38 | var Terminal = railroad.Terminal; 39 | var NonTerminal = railroad.NonTerminal; 40 | var Comment = railroad.Comment; 41 | var Skip = railroad.Skip; 42 | /* eslint-enable */ 43 | 44 | var result = eval(diagramSource).format(); // eslint-disable-line no-eval 45 | elem.innerHTML = ''; // eslint-disable-line 46 | result.addTo(elem); 47 | $e.find('railroad-diagram').attr('class', _railroadDiagrams3.default['railroad-diagram']); 48 | } catch (err) { 49 | console.log(err); 50 | } 51 | } 52 | /* eslint-enable */ -------------------------------------------------------------------------------- /lib/regex.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | // regular expressions 7 | 8 | /** 9 | * @author Steven Levithan (MIT license) 10 | */ 11 | /* eslint-disable */ 12 | var url = exports.url = /^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; 13 | /* eslint-enable */ 14 | 15 | // image 16 | var imageExt = exports.imageExt = /\.(png|jpg|gif)$/i; 17 | var flickr = exports.flickr = /flickr.com\/[a-z]+\/[a-zA-Z@_$!\d\-\]+/[\d]+/i; 18 | var instagram = exports.instagram = /instagram\.com\/p\/([\w\d]+)/i; 19 | var slideshare = exports.slideshare = /slideshare.net\/[a-zA-Z0-9_-]*\/[a-zA-Z0-9_-]*/i; 20 | 21 | // video 22 | var dailymotion = exports.dailymotion = /dailymotion.com\/video\/([\w\d]+)/i; 23 | var liveleak = exports.liveleak = /liveleak.com\/view\?i=[a-zA-Z0-9_]+/i; 24 | var ted = exports.ted = /ted.com\/(.+)/i; 25 | var ustream = exports.ustream = /ustream.tv\/[a-z/0-9]*/i; 26 | var vimeo = exports.vimeo = /vimeo.com\/(\d+)/i; 27 | var vine = exports.vine = /vine.co\/v\/([\w\d]+)/i; 28 | // TODO need params 29 | var youtube = exports.youtube = /youtube\.com/i; 30 | 31 | // audio 32 | var soundcloud = exports.soundcloud = /soundcloud.com\/([a-zA-Z0-9-_]+)\/([a-zA-Z0-9-_]+)/i; 33 | var spotify = exports.spotify = /spotify.com\/track\/[a-zA-Z0-9_]+/i; 34 | 35 | // maps 36 | var googleMap = exports.googleMap = /google\.com\/maps\/embed\?(.+)/i; 37 | 38 | // code 39 | var github = exports.github = /github.com\/([a-zA-Z0-9.-]+)\/([a-zA-Z0-9.-]+)/i; 40 | var gist = exports.gist = /gist.github.com\/[a-zA-Z0-9_-]+\/([a-zA-Z0-9]+)/i; 41 | var codepen = exports.codepen = /codepen.io\/([A-Za-z0-9_]+)\/pen\/([A-Za-z0-9_]+)/i; 42 | var ideone = exports.ideone = /ideone.com\/[a-zA-Z0-9]{6}/i; 43 | var jsbin = exports.jsbin = /jsbin.com\/[a-zA-Z0-9_]+\/[0-9_]+/i; 44 | var jsfiddle = exports.jsfiddle = /jsfiddle.net\/[a-zA-Z0-9_]+\/[a-zA-Z0-9_/]+/i; 45 | var plunker = exports.plunker = /plnkr.co\/edit\/[a-zA-Z0-9?=]+/i; 46 | 47 | // social media 48 | var twitter = exports.twitter = /twitter\.com\/\w+\/\w+\/\d+/i; -------------------------------------------------------------------------------- /lib/render.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = render; 7 | 8 | var _markdownIt = require('markdown-it'); 9 | 10 | var _markdownIt2 = _interopRequireDefault(_markdownIt); 11 | 12 | var _markdownItDeflist = require('markdown-it-deflist'); 13 | 14 | var _markdownItDeflist2 = _interopRequireDefault(_markdownItDeflist); 15 | 16 | var _markdownItSup = require('markdown-it-sup'); 17 | 18 | var _markdownItSup2 = _interopRequireDefault(_markdownItSup); 19 | 20 | var _markdownItSub = require('markdown-it-sub'); 21 | 22 | var _markdownItSub2 = _interopRequireDefault(_markdownItSub); 23 | 24 | var _markdownItFootnote = require('markdown-it-footnote'); 25 | 26 | var _markdownItFootnote2 = _interopRequireDefault(_markdownItFootnote); 27 | 28 | var _markdownItEmoji = require('markdown-it-emoji'); 29 | 30 | var _markdownItEmoji2 = _interopRequireDefault(_markdownItEmoji); 31 | 32 | var _twemoji = require('twemoji'); 33 | 34 | var _twemoji2 = _interopRequireDefault(_twemoji); 35 | 36 | var _linkify = require('./linkify'); 37 | 38 | var _linkify2 = _interopRequireDefault(_linkify); 39 | 40 | var _codeblock = require('./codeblock'); 41 | 42 | var _codeblock2 = _interopRequireDefault(_codeblock); 43 | 44 | var _embed = require('./embed'); 45 | 46 | var _embed2 = _interopRequireDefault(_embed); 47 | 48 | var _tasklist = require('./tasklist'); 49 | 50 | var _tasklist2 = _interopRequireDefault(_tasklist); 51 | 52 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 53 | 54 | var md = new _markdownIt2.default({ 55 | html: true, 56 | linkify: true, 57 | typographer: true 58 | }); 59 | 60 | (0, _linkify2.default)(md.linkify); 61 | 62 | // use plugins 63 | md.use(_codeblock2.default); 64 | md.use(_markdownItEmoji2.default); 65 | md.use(_markdownItDeflist2.default); 66 | md.use(_markdownItSub2.default); 67 | md.use(_markdownItSup2.default); 68 | md.use(_markdownItFootnote2.default); 69 | md.use(_embed2.default); 70 | md.use(_tasklist2.default); 71 | 72 | md.renderer.rules.emoji = function (tokens, idx) { 73 | return _twemoji2.default.parse(tokens[idx].content); 74 | }; 75 | 76 | // Renders given markdown text to HTML. 77 | function render(text) { 78 | return md.render(text || ''); 79 | } -------------------------------------------------------------------------------- /lib/style.scss: -------------------------------------------------------------------------------- 1 | .embed_container { 2 | position: relative; 3 | padding-bottom: 56.25%; 4 | height: 0; 5 | overflow: hidden; 6 | max-width: 100%; 7 | } 8 | 9 | .embed_container iframe, .embed_container object, .embed_container embed { 10 | border: 0; 11 | position: absolute; 12 | top: 0; 13 | left: 0; 14 | width: 100%; 15 | height: 100%; 16 | } 17 | 18 | .embed_container.instagram { 19 | padding-bottom: 120%; 20 | } 21 | 22 | .embed_container.slideshare { 23 | padding-bottom: 62.5821%; 24 | } 25 | 26 | .embed_container.soundcloud { 27 | width: 100%; 28 | height: 160px; 29 | padding-bottom: 0; 30 | } 31 | 32 | .embed_container.ted { 33 | padding-bottom: 75.0019%; 34 | } 35 | -------------------------------------------------------------------------------- /lib/tasklist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = plugin; 7 | 8 | var _lodash = require('lodash'); 9 | 10 | var _lodash2 = _interopRequireDefault(_lodash); 11 | 12 | var _classnames = require('classnames'); 13 | 14 | var _classnames2 = _interopRequireDefault(_classnames); 15 | 16 | var _githubMarkdown = require('github-markdown-css/github-markdown.css'); 17 | 18 | var _githubMarkdown2 = _interopRequireDefault(_githubMarkdown); 19 | 20 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 21 | 22 | var taskItemRegExp = /^\s*\[(\s|x?)]\s+/i; 23 | 24 | function checkbox(checked) { 25 | var attrs = checked ? 'checked' : ''; 26 | var className = _githubMarkdown2.default['task-list-item-checkbox']; 27 | return ''; 28 | } 29 | 30 | function matchTaskItem(tokens, i) { 31 | if (i + 1 < tokens.length && tokens[i].type === 'paragraph_open' && tokens[i + 1].type === 'inline') { 32 | var s = tokens[i + 1].content; 33 | return taskItemRegExp.exec(s); 34 | } 35 | return null; 36 | } 37 | 38 | function attrGet(token, name) { 39 | var i = token.attrIndex(name); 40 | return i < 0 ? '' : token.attrs[i]; 41 | } 42 | 43 | function getRenderFn(md, type) { 44 | var fn = md.renderer.rules[type]; 45 | if (_lodash2.default.isFunction(fn)) { 46 | return fn; 47 | } 48 | return function () { 49 | var _md$renderer; 50 | 51 | return (_md$renderer = md.renderer).renderToken.apply(_md$renderer, arguments); 52 | }; 53 | } 54 | 55 | function decorate(md, type, decoratorFn) { 56 | var renderToken = getRenderFn(md, type); 57 | md.renderer.rules[type] = function () { 58 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 59 | args[_key] = arguments[_key]; 60 | } 61 | 62 | // eslint-disable-line 63 | decoratorFn.apply(md.renderer, args); 64 | return renderToken.apply(md.renderer, args); 65 | }; 66 | } 67 | 68 | function plugin(md) { 69 | var currentMatch = null; 70 | 71 | decorate(md, 'list_item_open', function (tokens, i) { 72 | // eslint-disable-line 73 | currentMatch = matchTaskItem(tokens, i + 1); 74 | if (currentMatch) { 75 | var token = tokens[i]; 76 | var className = (0, _classnames2.default)(attrGet(token, 'class'), _githubMarkdown2.default['task-list-item']); 77 | token.attrSet('class', className); 78 | } 79 | }); 80 | 81 | decorate(md, 'list_item_close', function () { 82 | currentMatch = null; 83 | }); 84 | 85 | var renderInline = md.renderer.renderInline.bind(md.renderer); 86 | md.renderer.renderInline = function (tokens, options, env) { 87 | // eslint-disable-line 88 | var prefix = ''; 89 | if (currentMatch) { 90 | var done = currentMatch[1].toLowerCase() === 'x'; 91 | prefix = checkbox(done); 92 | currentMatch = null; 93 | var token = tokens[0]; 94 | token.content = token.content.replace(taskItemRegExp, ''); 95 | } 96 | return prefix + renderInline(tokens, options, env); 97 | }; 98 | } -------------------------------------------------------------------------------- /make.cmd: -------------------------------------------------------------------------------- 1 | set NODE_ENV=production 2 | mkdir lib 3 | cpx ".\src\**\*.scss" .\lib && babel src --out-dir lib 4 | -------------------------------------------------------------------------------- /makesite.sh: -------------------------------------------------------------------------------- 1 | npm run build:demo 2 | mkdir -p site 3 | cp -a ./content/. ./site/content 4 | cp -a ./static/. ./site/static 5 | cp -a ./index.html ./site/ 6 | # fix absolute links 7 | sed -i -- "s/\/static\//\.&/g" ./site/index.html 8 | -------------------------------------------------------------------------------- /markdown.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-markdown2", 3 | "version": "0.11.6", 4 | "description": "Yet another react component to render markdown.", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "start": "node devserver.js", 8 | "lint": "eslint --ext .js,.jsx .", 9 | "babel": "babel src --out-dir lib", 10 | "build:demo": "webpack --config webpack.config.js", 11 | "test": "npm run lint" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/reactbits/markdown.git" 16 | }, 17 | "keywords": [ 18 | "react", 19 | "markdown" 20 | ], 21 | "author": "Sergey Todyshev ", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/reactbits/markdown/issues" 25 | }, 26 | "homepage": "https://github.com/reactbits/markdown#readme", 27 | "dependencies": { 28 | "brace": "^0.10.0", 29 | "classnames": "^2.2.5", 30 | "css-effects": "^0.1.4", 31 | "embed-js": "4.2.3", 32 | "eve": "^0.5.4", 33 | "flowchart.js": "^1.6.6", 34 | "github-markdown-css": "^2.6.0", 35 | "hashtrie": "^1.0.0", 36 | "jquery": "^3.2.1", 37 | "js-sequence-diagrams": "git://github.com/sergeyt/js-sequence-diagrams.git#modularity", 38 | "katex": "^0.7.1", 39 | "linkify-it": "^2.0.3", 40 | "lodash": "^4.17.4", 41 | "markdown-it": "^8.3.1", 42 | "markdown-it-deflist": "^2.0.1", 43 | "markdown-it-emoji": "^1.3.0", 44 | "markdown-it-footnote": "^3.0.1", 45 | "markdown-it-sub": "^1.0.0", 46 | "markdown-it-sup": "^1.0.0", 47 | "prismjs-package": "^0.7.30", 48 | "railroad-diagrams": "^1.0.0", 49 | "raphael": "^2.2.7", 50 | "react": "^15.5.4", 51 | "react-ace": "^4.2.1", 52 | "react-diffview": "^0.1.46", 53 | "react-dom": "^15.5.4", 54 | "react-split-pane": "^0.1.63", 55 | "react-tabs": "^0.8.3", 56 | "strip-indent": "^2.0.0", 57 | "twemoji": "^2.2.5" 58 | }, 59 | "devDependencies": { 60 | "eslint": "^3.19.0", 61 | "react-bootstrap": "^0.31.0", 62 | "react-devpack": "^0.3.10", 63 | "react-sidebar": "^2.3.2" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/codeblock.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { renderToString } from 'react-dom/server'; 3 | import highlight from 'prismjs-package'; 4 | // TODO do that in prismjs-package 5 | import style from 'prismjs-package/themes/prism.css'; // eslint-disable-line 6 | import DiffView from 'react-diffview'; 7 | import { unescapeAll } from 'markdown-it/lib/common/utils'; 8 | 9 | const externalLangs = { 10 | seq: 'sequence', 11 | sequence: 'sequence', 12 | // TODO flow could be ambigious with flow.js 13 | flow: 'flowchart', 14 | flowchart: 'flowchart', 15 | railroad: 'railroad', 16 | tex: 'tex', 17 | }; 18 | 19 | const externalClass = { 20 | tex: 'tex', 21 | }; 22 | 23 | function external(code, lang) { 24 | const className = externalClass[lang] || 'diagram'; 25 | return `
${code}
`; 26 | } 27 | 28 | export function render(code, language) { 29 | const lang = (language || '').toLowerCase(); 30 | const externalLang = externalLangs[lang]; 31 | if (externalLang) { 32 | return external(code, externalLang); 33 | } 34 | if (lang.match(/^diff?/i)) { 35 | const view = ; 36 | return renderToString(view); 37 | } 38 | return highlight(code, language); 39 | } 40 | 41 | /* eslint-disable no-param-reassign */ 42 | export default function plugin(md) { 43 | md.renderer.rules.fence = function (tokens, idx, options) { 44 | const token = tokens[idx]; 45 | const info = token.info ? unescapeAll(token.info).trim() : ''; 46 | let langName = ''; 47 | 48 | if (info) { 49 | langName = info.split(/\s+/g)[0]; 50 | token.attrJoin('class', options.langPrefix + langName); 51 | } 52 | 53 | return render(token.content, langName); 54 | }; 55 | } 56 | /* eslint-enable */ 57 | -------------------------------------------------------------------------------- /src/content.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import stripIndent from 'strip-indent'; 4 | import styles from 'github-markdown-css/github-markdown.css'; 5 | import render from './render'; 6 | import renderInjections from './inject'; 7 | 8 | export default class Content extends Component { 9 | componentDidMount() { 10 | this.replaceBlocks(); 11 | } 12 | 13 | componentDidUpdate() { 14 | this.replaceBlocks(); 15 | } 16 | 17 | replaceBlocks() { 18 | const root = $(ReactDOM.findDOMNode(this)); // eslint-disable-line 19 | renderInjections(root.find('.injection')); 20 | } 21 | 22 | render() { 23 | const { source } = this.props; 24 | const html = render(stripIndent(source)); 25 | const className = styles['markdown-body']; 26 | return ; // eslint-disable-line 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/editor/editor.jsx: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import React, { Component, PropTypes } from 'react'; 3 | import SplitPane from 'react-split-pane'; 4 | import Ace from 'react-ace'; 5 | import 'brace/mode/markdown'; 6 | import 'brace/theme/github'; 7 | import Markdown from '../markdown'; 8 | import Toolbar from './toolbar'; 9 | import styles from './style.scss'; 10 | 11 | // TODO customizable tabs e.g. localization 12 | // TODO textarea mode without rich editor 13 | // TODO fix split mode 14 | // TODO sync scrollbars for editor and viewer 15 | 16 | export class MarkdownEditor extends Component { 17 | static propTypes = { 18 | value: PropTypes.string, 19 | onSave: PropTypes.func, 20 | onChange: PropTypes.func, 21 | }; 22 | 23 | static defaultProps = { 24 | value: '', 25 | onSave: _.noop, 26 | onChange: _.noop, 27 | }; 28 | 29 | constructor(props) { 30 | super(props); 31 | this.state = { 32 | mode: 'edit', 33 | value: props.value || '', 34 | }; 35 | } 36 | 37 | componentWillReceiveProps(nextProps) { 38 | this.setState({ value: nextProps.value }); 39 | } 40 | 41 | renderAce() { 42 | const { width, height } = this.props.style || {}; 43 | const onChange = (value) => { 44 | (this.props.onChange || _.noop)(value); 45 | this.setState({ value }); 46 | }; 47 | const aceProps = { 48 | mode: 'markdown', 49 | theme: 'github', 50 | value: this.state.value, 51 | onChange, 52 | width: width || '100%', 53 | height: height || '100%', 54 | }; 55 | return ; 56 | } 57 | 58 | renderNormalView() { 59 | if (this.state.mode === 'edit') { 60 | return this.renderAce(); 61 | } 62 | return ( 63 | 64 | ); 65 | } 66 | 67 | renderSplitView() { 68 | return ( 69 | 70 | {this.renderAce()} 71 | 72 | 73 | ); 74 | } 75 | 76 | render() { 77 | const { mode } = this.state; 78 | const { style, splitView } = this.props; 79 | const onAction = (type) => { 80 | // TODO implement formatting actions 81 | switch (type) { 82 | case 'mode': 83 | this.setState({ mode: mode === 'edit' ? 'preview' : 'edit' }); 84 | break; 85 | case 'save': 86 | (this.props.onSave || _.noop)(this.state.value); 87 | break; 88 | default: 89 | console.log(`unhandled action ${type}`); 90 | break; 91 | } 92 | }; 93 | return ( 94 |
95 | 96 | {splitView ? this.renderSplitView() : this.renderNormalView()} 97 |
98 | ); 99 | } 100 | } 101 | 102 | export default MarkdownEditor; 103 | -------------------------------------------------------------------------------- /src/editor/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './editor'; 2 | -------------------------------------------------------------------------------- /src/editor/split.scss: -------------------------------------------------------------------------------- 1 | .Resizer { 2 | background: #000; 3 | opacity: .2; 4 | z-index: 1; 5 | box-sizing: border-box; 6 | background-clip: padding-box; 7 | } 8 | 9 | .Resizer:hover { 10 | transition: all 2s ease; 11 | } 12 | 13 | .Resizer.horizontal { 14 | height: 11px; 15 | margin: -5px 0; 16 | border-top: 5px solid rgba(255, 255, 255, 0); 17 | border-bottom: 5px solid rgba(255, 255, 255, 0); 18 | cursor: row-resize; 19 | width: 100%; 20 | } 21 | 22 | .Resizer.horizontal:hover { 23 | border-top: 5px solid rgba(0, 0, 0, 0.5); 24 | border-bottom: 5px solid rgba(0, 0, 0, 0.5); 25 | } 26 | 27 | .Resizer.vertical { 28 | width: 11px; 29 | margin: 0 -5px; 30 | border-left: 5px solid rgba(255, 255, 255, 0); 31 | border-right: 5px solid rgba(255, 255, 255, 0); 32 | cursor: col-resize; 33 | height: 100%; 34 | } 35 | 36 | .Resizer.vertical:hover { 37 | border-left: 5px solid rgba(0, 0, 0, 0.5); 38 | border-right: 5px solid rgba(0, 0, 0, 0.5); 39 | } 40 | -------------------------------------------------------------------------------- /src/editor/style.scss: -------------------------------------------------------------------------------- 1 | @import "split"; 2 | 3 | .markdown_editor { 4 | min-height: 300px; 5 | } 6 | 7 | .markdown_editor .toolbar span { 8 | cursor: pointer; 9 | display: inline-block; 10 | margin: 2px; 11 | padding: 4px; 12 | min-width: 20px; 13 | min-height: 20px; 14 | text-align: center; 15 | vertical-align: middle; 16 | border-radius: 25%; 17 | } 18 | 19 | .markdown_editor .toolbar span:hover { 20 | background-color: lightgray; 21 | } 22 | -------------------------------------------------------------------------------- /src/editor/toolbar.jsx: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import React from 'react'; 3 | import classNames from 'classnames'; 4 | import { hint } from 'css-effects'; 5 | import style from './style.scss'; 6 | 7 | function Button(props) { 8 | const className = classNames(props.icon ? props.icon : null); 9 | const attrs = _.pick(props); 10 | let btnClass = ''; 11 | const btnAttrs = _.pick(props, 'onClick'); 12 | if (props.title) { 13 | btnClass = hint(); 14 | btnAttrs['data-hint'] = props.title; 15 | } 16 | return ( 17 | 18 | {props.text} 19 | 20 | ); 21 | } 22 | 23 | export default function Toolbar(props) { 24 | const action = type => () => props.onAction(type); 25 | const editMode = props.mode === 'edit'; 26 | const modeButton = { 27 | icon: editMode ? 'fa fa-eye' : 'fa fa-edit', 28 | title: editMode ? 'Preview' : 'Edit', 29 | onClick: action('mode'), 30 | }; 31 | // TODO disabled button states 32 | return ( 33 |
34 |
43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /src/embed.jsx: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import React from 'react'; 3 | import { renderToStaticMarkup } from 'react-dom/server'; 4 | import classNames from 'classnames'; 5 | import hashtrie from 'hashtrie'; 6 | import * as regex from './regex'; 7 | import styles from './style.scss'; 8 | import parseuri from './parseuri'; 9 | 10 | let rulemap = hashtrie.empty; 11 | 12 | function queryString(params) { 13 | return _.map(params, (v, k) => `${k}=${v}`).join('&'); 14 | } 15 | 16 | // embedding with embed.js 17 | function embed(url) { 18 | return `
${url}
`; 19 | } 20 | 21 | function iframe(props, containerProps = {}) { 22 | const iframeProps = { 23 | ...props, 24 | frameBorder: 0, 25 | webkitAllowFullScreen: true, 26 | mozallowfullscreen: true, 27 | allowFullScreen: true, 28 | }; 29 | delete iframeProps.className; 30 | delete iframeProps.afterFrame; 31 | const className = classNames(styles.embed_container, props.className); 32 | const container = ( 33 |
34 |