├── .npmignore
├── doc
├── .DS_Store
└── pic.png
├── .gitignore
├── LICENSE
├── package.json
├── README.md
└── src
└── simple_markdown_editor.js
/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 |
--------------------------------------------------------------------------------
/doc/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/seibelj/react-simple-markdown-editor/HEAD/doc/.DS_Store
--------------------------------------------------------------------------------
/doc/pic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/seibelj/react-simple-markdown-editor/HEAD/doc/pic.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
18 | .grunt
19 |
20 | # node-waf configuration
21 | .lock-wscript
22 |
23 | # Compiled binary addons (http://nodejs.org/api/addons.html)
24 | build/Release
25 |
26 | # Dependency directory
27 | node_modules
28 |
29 | # Optional npm cache directory
30 | .npm
31 |
32 | # Optional REPL history
33 | .node_repl_history
34 |
35 | dist
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 James Seibel
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 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-simple-markdown-editor",
3 | "version": "1.1.0",
4 | "description": "Simple markdown editor widget you can attach to any TextArea element to provide rich markdown capabilities. Requires few dependencies",
5 | "main": "./dist/simple_markdown_editor.js",
6 | "scripts": {
7 | "build": "babel src --presets babel-preset-es2015,babel-preset-react --out-dir dist"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/seibelj/react-simple-markdown-editor.git"
12 | },
13 | "keywords": [
14 | "react",
15 | "markdown",
16 | "editor",
17 | "simple",
18 | "remarkable"
19 | ],
20 | "author": "James Seibel",
21 | "license": "MIT",
22 | "bugs": {
23 | "url": "https://github.com/seibelj/react-simple-markdown-editor/issues"
24 | },
25 | "homepage": "https://github.com/seibelj/react-simple-markdown-editor#readme",
26 | "devDependencies": {
27 | "babel-cli": "^6.6.5",
28 | "babel-preset-es2015": "^6.6.0",
29 | "babel-preset-react": "^6.5.0"
30 | },
31 | "dependencies": {
32 | "lodash": "^4.6.1",
33 | "react": "^0.14.7"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Simple React Markdown Editor ##
2 | This makes it easy to add a simple markdown editing widget to any TextArea element.
3 | 
4 |
5 | ### Demo
6 | [CodePen Demo](http://codepen.io/seibelj/pen/rewRMe)
7 |
8 | ### Installation
9 | `npm install react-simple-markdown-editor`
10 |
11 | ### Features
12 |
13 | - Entirely customizable. Modify CSS easily with props, or add custom classes and modify CSS with stylesheets. Define which buttons are visible.
14 | - The only package dependencies are `react` and `lodash`, minimizing risk.
15 |
16 |
17 |
18 | ### Usage
19 |
20 | In your code:
21 |
22 | ES6:
23 | ```javascript
24 | import {SimpleMarkdownEditor} from 'react-simple-markdown-editor';
25 | ```
26 |
27 | Non-ES6:
28 | ```javascript
29 | var SimpleMarkdownEditor = require('react-simple-markdown-editor');
30 | ```
31 |
32 | In your React `render()` function:
33 |
34 | ``
35 |
36 | ### Rendering Markdown
37 | Use another library like [react-remarkable](https://github.com/acdlite/react-remarkable) in combination with this. Then set the `source` of the remarkable component to the value of your TextArea element.
38 |
39 | ### API
40 | Props:
41 | ```javascript
42 | SimpleMarkdownEditor.propTypes = {
43 | // Required props
44 | textAreaID: PropTypes.string.isRequired,
45 |
46 | // Optional props
47 | styles: PropTypes.object,
48 | containerClass: PropTypes.string,
49 | buttonClass: PropTypes.string,
50 | enabledButtons: PropTypes.object,
51 | buttonHtmlText: PropTypes.object,
52 | additionalProps: PropTypes.object
53 | };
54 | ```
55 | `textAreaID` (String, Required): The ID of the TextArea element you want the editor attached to. When you press buttons in this widget, the text in this TextArea will be modified.
56 |
57 | `styles`: (Object, optional): Used to overwrite inline CSS without using your own stylesheets.
58 |
59 | Existing properties:
60 |
61 | ```javascript
62 | container: {
63 |
64 | },
65 | button: {
66 | fontFamily: 'Georgia, serif',
67 | backgroundColor: '#333536',
68 | color: 'white',
69 | marginRight: '5px',
70 | float: 'left',
71 | width: '25px',
72 | borderRadius: '4px',
73 | textAlign: 'center',
74 | cursor: 'pointer'
75 | }
76 | ```
77 |
78 | For instance, if you want to add a border to each button:
79 | ``
80 |
81 | `containerClass` and `buttonClass` (String, optional): Provide classes to the container and button elements, so you can overwrite them using your own CSS stylesheets. An alternative to setting the `styles` prop.
82 |
83 | `enabledButtons`: (Object, optional): Hide any buttons you don't want to show. All of them default to showing. Buttons:
84 |
85 | ```javascript
86 | {
87 | bold: true,
88 | italic: true,
89 | strike: true,
90 | code: true,
91 | quote: true,
92 | h1: true,
93 | h2: true,
94 | h3: true,
95 | bullet: true,
96 | link: true,
97 | image: true
98 | }
99 | ```
100 | For instance, if you want to hide the link button:
101 | ``
102 |
103 | `buttonHtmlText`: (Object, optional): Change the display text of any buttons, including any HTML markup. Defaults:
104 |
105 | ```javascript
106 | {
107 | bold: 'B',
108 | italic: 'I',
109 | strike: 'S',
110 | code: '< >',
111 | quote: '“ ”',
112 | h1: 'H1',
113 | h2: 'H2',
114 | h3: 'H3',
115 | bullet: '•',
116 | link: '#',
117 | image: '[i]'
118 | }
119 | ```
120 | For instance, if you want to change `code` to be a square `quote` to be 2 right arrows:
121 | ``
122 |
123 | `additionalProps`: (Object, optional): Add arbitrary props to any button. For instance:
124 | ``
125 |
126 | ### License
127 | MIT, use for free. If you like this, give it a star.
128 |
--------------------------------------------------------------------------------
/src/simple_markdown_editor.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import {merge} from 'lodash';
3 |
4 | class SimpleMarkdownEditor extends React.Component {
5 |
6 | wrapText(symbol, endSymbol, insertAfter) {
7 |
8 | if (!endSymbol) {
9 | endSymbol = symbol;
10 | }
11 |
12 | let elem = document.getElementById(this.props.textAreaID),
13 | start = elem.selectionStart,
14 | end = elem.selectionEnd,
15 | text = elem.value;
16 |
17 | let afterText = insertAfter ? insertAfter : '';
18 |
19 | elem.value = text.substring(0, start) + symbol + text.substring(start, end) + endSymbol + afterText + text.substring(end, text.length);
20 | elem.focus();
21 | elem.setSelectionRange(start + symbol.length, end + endSymbol.length);
22 | }
23 |
24 | insertBold() {
25 | this.wrapText('**');
26 | }
27 |
28 | insertItalics() {
29 | this.wrapText('_');
30 | }
31 |
32 | insertStrike() {
33 | this.wrapText('~~');
34 | }
35 |
36 | insertCode() {
37 | this.wrapText('`');
38 | }
39 |
40 | insertAtBeginningOfLine(symbol) {
41 | let elem = document.getElementById(this.props.textAreaID),
42 | start = elem.selectionStart,
43 | end = elem.selectionEnd,
44 | text = elem.value;
45 |
46 | let newLineIndex = text.lastIndexOf('\n', start - 1);
47 | if (newLineIndex === -1) {
48 | elem.value = symbol + text;
49 | }
50 | else {
51 | elem.value = text.substring(0, newLineIndex + 1) + symbol + text.substring(newLineIndex + 1, text.length);
52 | }
53 | elem.focus();
54 | elem.setSelectionRange(start + symbol.length, end + symbol.length);
55 | }
56 |
57 | insertH1() {
58 | this.insertAtBeginningOfLine('# ');
59 | }
60 |
61 | insertH2() {
62 | this.insertAtBeginningOfLine('## ');
63 | }
64 |
65 | insertH3() {
66 | this.insertAtBeginningOfLine('### ');
67 | }
68 |
69 | insertQuote() {
70 | this.insertAtBeginningOfLine('> ');
71 | }
72 |
73 | insertBullet() {
74 | this.insertAtBeginningOfLine('* ');
75 | }
76 |
77 | insertLink() {
78 | let elem = document.getElementById(this.props.textAreaID),
79 | start = elem.selectionStart,
80 | end = elem.selectionEnd,
81 | text = elem.value,
82 | link = "(http://www.mylink.com/)";
83 |
84 | if (start === end) {
85 | elem.value = text.substring(0, start) + "[Link Text]" + link + text.substring(start, text.length);
86 | elem.focus();
87 | elem.setSelectionRange(start, start);
88 | }
89 | else {
90 | this.wrapText('[', ']', link);
91 | }
92 | }
93 |
94 | insertImage() {
95 | let elem = document.getElementById(this.props.textAreaID),
96 | start = elem.selectionStart,
97 | end = elem.selectionEnd,
98 | text = elem.value,
99 | link = "(http://myhost.com/my_image.jpg)";
100 |
101 | if (start === end) {
102 | elem.value = text.substring(0, start) + "![Image Description]" + link + text.substring(start, text.length);
103 | elem.focus();
104 | elem.setSelectionRange(start, start);
105 | }
106 | else {
107 | this.wrapText('![', ']', link);
108 | }
109 | }
110 |
111 | render() {
112 |
113 | let styles = merge({}, this.constructor.styles, this.props.styles),
114 | enabledButtons = merge({}, this.constructor.enabledButtons, this.props.enabledButtons),
115 | buttonHtmlText = merge({}, this.constructor.buttonHtmlText, this.props.buttonHtmlText),
116 | additionalProps = merge({}, this.constructor.additionalProps, this.props.additionalProps);
117 |
118 | return (
119 |
120 | {enabledButtons.bold &&
121 |
123 | }
124 | {enabledButtons.italic &&
125 |
127 | }
128 | {enabledButtons.strike &&
129 |
131 | }
132 | {enabledButtons.code &&
133 |
135 | }
136 | {enabledButtons.quote &&
137 |
139 | }
140 | {enabledButtons.h1 &&
141 |
143 | }
144 | {enabledButtons.h2 &&
145 |
147 | }
148 | {enabledButtons.h3 &&
149 |
151 | }
152 | {enabledButtons.bullet &&
153 |
155 | }
156 | {enabledButtons.link &&
157 |
159 | }
160 | {enabledButtons.image &&
161 |
163 | }
164 |
165 |
166 | );
167 | }
168 | }
169 |
170 | SimpleMarkdownEditor.styles = {
171 | container: {
172 |
173 | },
174 | button: {
175 | fontFamily: 'Georgia, serif',
176 | backgroundColor: '#333536',
177 | color: 'white',
178 | marginRight: '5px',
179 | float: 'left',
180 | width: '25px',
181 | borderRadius: '4px',
182 | textAlign: 'center',
183 | cursor: 'pointer'
184 | }
185 | };
186 |
187 | SimpleMarkdownEditor.enabledButtons = {
188 | bold: true,
189 | italic: true,
190 | strike: true,
191 | code: true,
192 | quote: true,
193 | h1: true,
194 | h2: true,
195 | h3: true,
196 | bullet: true,
197 | link: true,
198 | image: true
199 | }
200 |
201 | SimpleMarkdownEditor.buttonHtmlText = {
202 | bold: 'B',
203 | italic: 'I',
204 | strike: 'S',
205 | code: '< >',
206 | quote: '“ ”',
207 | h1: 'H1',
208 | h2: 'H2',
209 | h3: 'H3',
210 | bullet: '•',
211 | link: '#',
212 | image: '[i]'
213 | }
214 |
215 | SimpleMarkdownEditor.additionalProps = {
216 | bold: {},
217 | italic: {},
218 | strike: {},
219 | code: {},
220 | quote: {},
221 | h1: {},
222 | h2: {},
223 | h3: {},
224 | bullet: {},
225 | link: {},
226 | image: {}
227 | }
228 |
229 | SimpleMarkdownEditor.propTypes = {
230 | // Required props
231 | textAreaID: PropTypes.string.isRequired,
232 |
233 | // Optional props
234 | styles: PropTypes.object,
235 | containerClass: PropTypes.string,
236 | buttonClass: PropTypes.string,
237 | enabledButtons: PropTypes.object,
238 | buttonHtmlText: PropTypes.object,
239 | additionalProps: PropTypes.object
240 | };
241 |
242 | exports.SimpleMarkdownEditor = SimpleMarkdownEditor;
--------------------------------------------------------------------------------