├── .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 | [](https://badge.fury.io/js/react-markdown2)
2 | [](https://travis-ci.org/reactbits/markdown)
3 | [](https://www.npmjs.com/package/react-markdown2)
4 |
5 | [](https://david-dm.org/reactbits/markdown)
6 | [](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 |
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 | 
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 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
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 |
35 | {props.afterFrame || null}
36 |
37 | );
38 | return renderToStaticMarkup(container);
39 | }
40 |
41 | function canApplyRule(test, url) {
42 | if (typeof test === 'function') {
43 | return test(url);
44 | }
45 | return test.exec(url);
46 | }
47 |
48 | function makeRule(test, render) {
49 | if (test === undefined) {
50 | throw new Error('nre');
51 | }
52 | if (_.isArray(test)) {
53 | const fn = url => _.some(test, t => canApplyRule(t, url));
54 | return { test: fn, render };
55 | }
56 | return { test, render };
57 | }
58 |
59 | function rule(host, test, render) {
60 | const value = makeRule(test, render);
61 | if (host) {
62 | rulemap = rulemap.set(host, value);
63 | rulemap = rulemap.set(`www.${host}`, value);
64 | }
65 | return value;
66 | }
67 |
68 | function rules(host, set) { // eslint-disable-line
69 | rulemap = rulemap.set(host, set);
70 | rulemap = rulemap.set(`www.${host}`, set);
71 | }
72 |
73 | const image = rule('', regex.imageExt, url => `
`);
74 |
75 | // TODO support watch URLs
76 | rule('youtube.com', regex.youtube, url => (
77 | iframe({ className: styles.youtube, src: url })
78 | ));
79 |
80 | rule('vimeo.com', regex.vimeo, (url, match) => {
81 | const src = `http://player.vimeo.com/video/${match[1]}`;
82 | return iframe({ className: styles.vimeo, src });
83 | });
84 |
85 | rule('dailymotion.com', regex.dailymotion, (url, match) => {
86 | const src = `http://www.dailymotion.com/embed/video/${match[1]}`;
87 | return iframe({ className: styles.daylymotion, src });
88 | });
89 |
90 | const vineScript = ``; // eslint-disable-line
91 |
92 | rule('vine.co', regex.vine, (url, match) => {
93 | const src = `https://vine.co/v/${match[1]}/embed/simple`;
94 | return iframe({ className: styles.vine, src, afterFrame: vineScript });
95 | });
96 |
97 | rule('liveleak.com', regex.liveleak, embed);
98 |
99 | rule('ted.com', regex.ted, (url, match) => {
100 | const src = `https://embed-ssl.ted.com/${match[1]}`;
101 | return iframe({ className: styles.ted, src });
102 | });
103 |
104 | rule('ustream.tv', regex.ustream, embed);
105 |
106 | rule('google.com', regex.googleMap, (url, match) => {
107 | const src = `https://www.google.com/maps/embed?${match[1]}`;
108 | return iframe({ className: styles.google_map, src });
109 | });
110 |
111 | // image
112 | rule('instagram.com', regex.instagram, (url, match) => {
113 | const src = `//instagram.com/p/${match[1]}/embed/`;
114 | return iframe({ className: styles.instagram, src, scrolling: 'no' });
115 | });
116 |
117 | rule('flickr.com', regex.flickr, embed);
118 | // const src = `https://www.slideshare.net/slideshow/embed_code/key/zTckhjEe9nT5j8`;
119 | rule('slideshare.net', regex.slideshare, embed);
120 |
121 | // rule('imgur.com', regex.imgur, (url, match) => {
122 | //
123 | // How to tell when your cat is fully charged
124 | //
125 | //
126 | // });
127 |
128 | // audio
129 |
130 | const soundcloudParams = queryString({
131 | auto_play: 'false',
132 | hide_related: 'false',
133 | show_comments: 'true',
134 | show_user: 'true',
135 | show_reposts: 'false',
136 | visual: 'false',
137 | download: 'false',
138 | color: 'f50000',
139 | theme_color: 'f50000',
140 | });
141 |
142 | rule('soundcloud.com', regex.soundcloud, (url, match) => {
143 | const player = `https://w.soundcloud.com/player/?url=soundcloud.com/${match[1]}/${match[2]}&`;
144 | const src = `${player}${soundcloudParams}`;
145 | return iframe({ className: styles.soundcloud, src, scrolling: 'no' });
146 | });
147 |
148 | rule('spotify.com', regex.spotify, embed);
149 | // code
150 | rule('github.com', regex.github, embed);
151 | rule('gist.github.com', regex.gist, embed);
152 |
153 | rule('codepen.io', regex.codepen, (url, match) => {
154 | const slug = match[2];
155 | const 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
156 | return iframe({ className: styles.codepen, src });
157 | });
158 |
159 | rule('ideone.com', regex.ideone, embed);
160 | rule('jsbin.com', regex.jsbin, embed);
161 | rule('jsfiddle.net', regex.jsfiddle, embed);
162 | rule('plnkr.co', regex.plunker, embed);
163 | // social
164 | rule('twitter.com', regex.twitter, embed);
165 |
166 | function applyRule(self, url) {
167 | if (typeof self.test === 'function') {
168 | if (self.test(url)) {
169 | return self.render(url);
170 | }
171 | return undefined;
172 | }
173 | const match = self.test.exec(url);
174 | if (match) {
175 | return self.render(url, match);
176 | }
177 | return undefined;
178 | }
179 |
180 | function embedURL(url) {
181 | if (!url) return undefined;
182 |
183 | const uri = parseuri(url);
184 | if (!uri) return undefined;
185 |
186 | if (url.match(image.test)) {
187 | return image.render(url);
188 | }
189 |
190 | const set = rulemap.get(uri.host);
191 | if (!set) return undefined;
192 |
193 | if (_.isArray(set)) {
194 | for (let i = 0; i < set.length; i += 1) {
195 | const result = applyRule(set[i], url);
196 | if (result) return result;
197 | }
198 | return undefined;
199 | }
200 |
201 | return applyRule(set, url);
202 | }
203 |
204 | /* eslint-disable no-param-reassign */
205 | export default function plugin(md) {
206 | let skip = false;
207 | md.renderer.rules.link_open = function link(tokens, i, options) {
208 | if (i + 2 < tokens.length
209 | && tokens[i + 1].type === 'text'
210 | && tokens[i + 2].type === 'link_close') {
211 | const result = embedURL(tokens[i + 1].content);
212 | if (typeof result === 'string') {
213 | skip = true;
214 | return result;
215 | }
216 | }
217 | return md.renderer.renderToken(tokens, i, options);
218 | };
219 |
220 | const renderText = md.renderer.rules.text;
221 | md.renderer.rules.text = function link(tokens, i, options, self) {
222 | if (skip) return '';
223 | return renderText(tokens, i, options, self);
224 | };
225 |
226 | md.renderer.rules.link_close = function link(tokens, i, options) {
227 | if (skip) {
228 | skip = false;
229 | return '';
230 | }
231 | return md.renderer.renderToken(tokens, i, options);
232 | };
233 | }
234 | /* eslint-enable */
235 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export { default as Markdown } from './markdown';
2 | export { default } from './markdown';
3 | export { default as MarkdownEditor } from './editor';
4 | export { default as render } from './render';
5 | export { linkTo } from './linkify';
6 |
--------------------------------------------------------------------------------
/src/inject.js:
--------------------------------------------------------------------------------
1 | /*eslint-disable*/
2 | import sequenceDiagram from 'js-sequence-diagrams';
3 | import flowchart from 'flowchart.js';
4 | /*eslint-enable*/
5 | import katex from 'katex';
6 | import railroad from './railroad';
7 | // import katexStyles from '../bower_components/KaTeX/dist/katex.min.css'; // eslint-disable-line
8 | // import EmbedJS from 'embed-js/src/js/main.js'; // eslint-disable-line
9 | // import embedStyles from 'embed-js/src/embed.css'; // eslint-disable-line
10 |
11 | function inject(element) {
12 | const $e = $(element);
13 | if ($e.is('.diagram')) {
14 | const lang = $e.data('lang');
15 | switch (lang) {
16 | case 'sequence':
17 | $e.sequenceDiagram({ theme: 'hand' });
18 | break;
19 | case 'flowchart':
20 | $e.flowChart();
21 | break;
22 | case 'railroad':
23 | railroad(element);
24 | break;
25 | default:
26 | break;
27 | }
28 | return;
29 | }
30 | if ($e.is('.tex')) {
31 | try {
32 | const text = $e.text();
33 | katex.render(text, element);
34 | } catch (e) {
35 | console.log('katex failed:', e);
36 | }
37 | return;
38 | }
39 | if ($e.is('.embedjs')) {
40 | // const em = new window.EmbedJS({
41 | // element,
42 | // });
43 | // em.render();
44 | }
45 | }
46 |
47 | export default function render(elems) {
48 | $.each(elems, (i, e) => inject(e));
49 | }
50 |
--------------------------------------------------------------------------------
/src/linkify.js:
--------------------------------------------------------------------------------
1 | const linkTo = {
2 | user: 'https://twitter.com/',
3 | issue: 'https://github.com/reactbits/markdown/issues/',
4 | hashtag: 'https://twitter.com/hashtag/',
5 | };
6 |
7 | export { linkTo };
8 |
9 | export default function configureLinkifier(linkify) {
10 | linkify.set({
11 | fuzzyLink: true,
12 | fuzzyIP: false,
13 | fuzzyEmail: true,
14 | });
15 |
16 | const rexprs = { };
17 |
18 | function register(prefix, key, replaceUrl) {
19 | linkify.add(prefix, {
20 | validate: function validate(text, pos, self) {
21 | const tail = text.slice(pos);
22 |
23 | let re = rexprs[key];
24 | if (!re) {
25 | re = new RegExp(`^([a-zA-Z0-9_]){1,15}(?!_)(?=$|${self.re.src_ZPCc})`);
26 | rexprs[key] = re;
27 | }
28 |
29 | if (re.test(tail)) {
30 | // Linkifier allows punctuation chars before prefix,
31 | // but we additionally disable `@` ("@@mention" is invalid)
32 | if (pos >= 2 && tail[pos - 2] === prefix) {
33 | return false;
34 | }
35 | return tail.match(re)[0].length;
36 | }
37 |
38 | return 0;
39 | },
40 |
41 | /*eslint-disable*/
42 | normalize: function normalize(match) {
43 | match.url = replaceUrl(match.url);
44 | },
45 | /*eslint-enable*/
46 | });
47 | }
48 |
49 | register('@', 'mention', url => linkTo.user + url.replace(/^@/, ''));
50 | register('issue ', 'issue', url => linkTo.issue + url.replace(/^issue /, ''));
51 | register('#', 'hashtag', url => linkTo.hashtag + url.replace(/^#/, ''));
52 | }
53 |
--------------------------------------------------------------------------------
/src/markdown.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Content from './content';
3 |
4 | export default class Markdown extends Component {
5 | render() {
6 | const { source } = this.props;
7 | const className = this.props.className || 'markdown';
8 | const style = this.props.style || {};
9 | let content;
10 | if (source) {
11 | content = ;
12 | } else {
13 | content = React.Children.map(this.props.children, (child, i) => {
14 | if (typeof child === 'string') {
15 | return ;
16 | }
17 | return child;
18 | });
19 | }
20 | return (
21 | {content}
22 | );
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/parseuri.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Parses an URI
3 | *
4 | * @author Steven Levithan (MIT license)
5 | * @api private
6 | */
7 |
8 | /* eslint-disable */
9 | export const regex = /^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
10 | /* eslint-enable */
11 |
12 | const parts = [
13 | 'source',
14 | 'protocol',
15 | 'authority',
16 | 'userInfo',
17 | 'user',
18 | 'password',
19 | 'host',
20 | 'port',
21 | 'relative',
22 | 'path',
23 | 'directory',
24 | 'file',
25 | 'query',
26 | 'anchor',
27 | ];
28 |
29 | export default function parseuri(str) {
30 | const src = str;
31 | const b = str.indexOf('[');
32 | const e = str.indexOf(']');
33 |
34 | if (b !== -1 && e !== -1) {
35 | str = str.substring(0, b) + str.substring(b, e).replace(/:/g, ';') + str.substring(e, str.length); // eslint-disable-line
36 | }
37 |
38 | const m = regex.exec(str || '');
39 | if (!m) {
40 | return null;
41 | }
42 |
43 | const uri = {};
44 | let i = 14;
45 |
46 | while (i--) { // eslint-disable-line
47 | uri[parts[i]] = m[i] || '';
48 | }
49 |
50 | if (b !== -1 && e !== -1) {
51 | uri.source = src;
52 | uri.host = uri.host.substring(1, uri.host.length - 1).replace(/;/g, ':');
53 | uri.authority = uri.authority.replace('[', '').replace(']', '').replace(/;/g, ':');
54 | uri.ipv6uri = true;
55 | }
56 |
57 | return uri;
58 | }
59 |
--------------------------------------------------------------------------------
/src/railroad.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import * as railroad from 'railroad-diagrams';
3 | /* eslint-enable */
4 | import styles from 'railroad-diagrams/railroad-diagrams.css';
5 |
6 | export default function render(elem) {
7 | const $e = $(elem);
8 | try {
9 | const diagramSource = $e.text();
10 |
11 | // context for eval
12 | /* eslint-disable */
13 | var Diagram = railroad.Diagram;
14 | var ComplexDiagram = railroad.ComplexDiagram;
15 | var Sequence = railroad.Sequence;
16 | var Stack = railroad.Stack;
17 | var OptionalSequence = railroad.OptionalSequence;
18 | var Choice = railroad.Choice;
19 | var MultipleChoice = railroad.MultipleChoice;
20 | var Optional = railroad.Optional;
21 | var OneOrMore = railroad.OneOrMore;
22 | var ZeroOrMore = railroad.ZeroOrMore;
23 | var Terminal = railroad.Terminal;
24 | var NonTerminal = railroad.NonTerminal;
25 | var Comment = railroad.Comment;
26 | var Skip = railroad.Skip;
27 | /* eslint-enable */
28 |
29 | const result = eval(diagramSource).format(); // eslint-disable-line no-eval
30 | elem.innerHTML = ''; // eslint-disable-line
31 | result.addTo(elem);
32 | $e.find('railroad-diagram').attr('class', styles['railroad-diagram']);
33 | } catch (err) {
34 | console.log(err);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/regex.js:
--------------------------------------------------------------------------------
1 | // regular expressions
2 |
3 | /**
4 | * @author Steven Levithan (MIT license)
5 | */
6 | /* eslint-disable */
7 | export const url = /^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
8 | /* eslint-enable */
9 |
10 | // image
11 | export const imageExt = /\.(png|jpg|gif)$/i;
12 | export const flickr = /flickr.com\/[a-z]+\/[a-zA-Z@_$!\d\-\]+/[\d]+/i;
13 | export const instagram = /instagram\.com\/p\/([\w\d]+)/i;
14 | export const slideshare = /slideshare.net\/[a-zA-Z0-9_-]*\/[a-zA-Z0-9_-]*/i;
15 |
16 | // video
17 | export const dailymotion = /dailymotion.com\/video\/([\w\d]+)/i;
18 | export const liveleak = /liveleak.com\/view\?i=[a-zA-Z0-9_]+/i;
19 | export const ted = /ted.com\/(.+)/i;
20 | export const ustream = /ustream.tv\/[a-z/0-9]*/i;
21 | export const vimeo = /vimeo.com\/(\d+)/i;
22 | export const vine = /vine.co\/v\/([\w\d]+)/i;
23 | // TODO need params
24 | export const youtube = /youtube\.com/i;
25 |
26 | // audio
27 | export const soundcloud = /soundcloud.com\/([a-zA-Z0-9-_]+)\/([a-zA-Z0-9-_]+)/i;
28 | export const spotify = /spotify.com\/track\/[a-zA-Z0-9_]+/i;
29 |
30 | // maps
31 | export const googleMap = /google\.com\/maps\/embed\?(.+)/i;
32 |
33 | // code
34 | export const github = /github.com\/([a-zA-Z0-9.-]+)\/([a-zA-Z0-9.-]+)/i;
35 | export const gist = /gist.github.com\/[a-zA-Z0-9_-]+\/([a-zA-Z0-9]+)/i;
36 | export const codepen = /codepen.io\/([A-Za-z0-9_]+)\/pen\/([A-Za-z0-9_]+)/i;
37 | export const ideone = /ideone.com\/[a-zA-Z0-9]{6}/i;
38 | export const jsbin = /jsbin.com\/[a-zA-Z0-9_]+\/[0-9_]+/i;
39 | export const jsfiddle = /jsfiddle.net\/[a-zA-Z0-9_]+\/[a-zA-Z0-9_/]+/i;
40 | export const plunker = /plnkr.co\/edit\/[a-zA-Z0-9?=]+/i;
41 |
42 | // social media
43 | export const twitter = /twitter\.com\/\w+\/\w+\/\d+/i;
44 |
--------------------------------------------------------------------------------
/src/render.js:
--------------------------------------------------------------------------------
1 | import MarkdownIt from 'markdown-it';
2 | import deflist from 'markdown-it-deflist';
3 | import mdsup from 'markdown-it-sup';
4 | import mdsub from 'markdown-it-sub';
5 | import footnotes from 'markdown-it-footnote';
6 | import emoji from 'markdown-it-emoji';
7 | import twemoji from 'twemoji';
8 | import configureLinkifier from './linkify';
9 | import codeblock from './codeblock';
10 | import embed from './embed';
11 | import tasklist from './tasklist';
12 |
13 | const md = new MarkdownIt({
14 | html: true,
15 | linkify: true,
16 | typographer: true,
17 | });
18 |
19 | configureLinkifier(md.linkify);
20 |
21 | // use plugins
22 | md.use(codeblock);
23 | md.use(emoji);
24 | md.use(deflist);
25 | md.use(mdsub);
26 | md.use(mdsup);
27 | md.use(footnotes);
28 | md.use(embed);
29 | md.use(tasklist);
30 |
31 | md.renderer.rules.emoji = (tokens, idx) => twemoji.parse(tokens[idx].content);
32 |
33 | // Renders given markdown text to HTML.
34 | export default function render(text) {
35 | return md.render(text || '');
36 | }
37 |
--------------------------------------------------------------------------------
/src/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 |
--------------------------------------------------------------------------------
/src/tasklist.jsx:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import classNames from 'classnames';
3 | import styles from 'github-markdown-css/github-markdown.css';
4 |
5 | const taskItemRegExp = /^\s*\[(\s|x?)]\s+/i;
6 |
7 | function checkbox(checked) {
8 | const attrs = checked ? 'checked' : '';
9 | const className = styles['task-list-item-checkbox'];
10 | return ``;
11 | }
12 |
13 | function matchTaskItem(tokens, i) {
14 | if (i + 1 < tokens.length
15 | && tokens[i].type === 'paragraph_open'
16 | && tokens[i + 1].type === 'inline') {
17 | const s = tokens[i + 1].content;
18 | return taskItemRegExp.exec(s);
19 | }
20 | return null;
21 | }
22 |
23 | function attrGet(token, name) {
24 | const i = token.attrIndex(name);
25 | return i < 0 ? '' : token.attrs[i];
26 | }
27 |
28 | function getRenderFn(md, type) {
29 | const fn = md.renderer.rules[type];
30 | if (_.isFunction(fn)) {
31 | return fn;
32 | }
33 | return (...args) => md.renderer.renderToken(...args);
34 | }
35 |
36 | function decorate(md, type, decoratorFn) {
37 | const renderToken = getRenderFn(md, type);
38 | md.renderer.rules[type] = function (...args) { // eslint-disable-line
39 | decoratorFn.apply(md.renderer, args);
40 | return renderToken.apply(md.renderer, args);
41 | };
42 | }
43 |
44 | export default function plugin(md) {
45 | let currentMatch = null;
46 |
47 | decorate(md, 'list_item_open', (tokens, i, ...args) => { // eslint-disable-line
48 | currentMatch = matchTaskItem(tokens, i + 1);
49 | if (currentMatch) {
50 | const token = tokens[i];
51 | const className = classNames(attrGet(token, 'class'), styles['task-list-item']);
52 | token.attrSet('class', className);
53 | }
54 | });
55 |
56 | decorate(md, 'list_item_close', () => { currentMatch = null; });
57 |
58 | const renderInline = md.renderer.renderInline.bind(md.renderer);
59 | md.renderer.renderInline = function (tokens, options, env) { // eslint-disable-line
60 | let prefix = '';
61 | if (currentMatch) {
62 | const done = currentMatch[1].toLowerCase() === 'x';
63 | prefix = checkbox(done);
64 | currentMatch = null;
65 | const token = tokens[0];
66 | token.content = token.content.replace(taskItemRegExp, '');
67 | }
68 | return prefix + renderInline(tokens, options, env);
69 | };
70 | }
71 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const makeWebpackConfig = require('react-devpack').makeWebpackConfig;
2 |
3 | module.exports = makeWebpackConfig({
4 | entry: './demo/index',
5 | resolve: {
6 | alias: {
7 | 'dev/raphael.core.js': './dev/raphael.core.js',
8 | 'raphael.core': './raphael.core.js',
9 | 'raphael.svg': './dev/raphael.svg.js',
10 | 'raphael.vml': './dev/raphael.vml.js',
11 | },
12 | },
13 | });
14 |
--------------------------------------------------------------------------------