Skip to content

Commit 78e6c0a

Browse files
committed
Making commands extensible
1 parent de2cf26 commit 78e6c0a

File tree

3 files changed

+131
-112
lines changed

3 files changed

+131
-112
lines changed

demo/App.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { Component } from 'react';
22
import ReactMde from '../src/ReactMde';
3+
import ReactMdeCommands from '../src/ReactMdeCommands'
34

45
class App extends Component {
56

@@ -12,9 +13,10 @@ class App extends Component {
1213
}
1314

1415
render() {
16+
let commands = ReactMdeCommands.getDefaultCommands()
1517
return (
1618
<div className="container">
17-
<ReactMde value={this.state.mdeValue} onChange={this.handleValueChange.bind(this)} />
19+
<ReactMde value={this.state.mdeValue} onChange={this.handleValueChange.bind(this)} commands={commands} />
1820
</div>
1921
);
2022
}

src/ReactMde.js

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,12 @@ const HeaderGroup = (props) => (
3636
);
3737

3838
const HeaderItem = ({icon, onClick, tooltip}) => {
39-
var x = React.isValidElement(icon) ? icon : <i className={`fa fa-${icon}`} aria-hidden="true"></i>
39+
40+
// if icon is a text, print a font-awesome <i/>, otherwise, consider it a React component and print it
41+
var iconElement = React.isValidElement(icon) ? icon : <i className={`fa fa-${icon}`} aria-hidden="true"></i>
4042

4143
let buttonProps = {};
42-
if(tooltip) {
44+
if (tooltip) {
4345
buttonProps = {
4446
'aria-label': tooltip,
4547
className: 'tooltipped'
@@ -48,7 +50,7 @@ const HeaderItem = ({icon, onClick, tooltip}) => {
4850
return (
4951
<li className="mde-header-item">
5052
<button type="button" {...buttonProps} onClick={onClick}>
51-
{x}
53+
{iconElement}
5254
</button>
5355
</li>
5456
);
@@ -66,6 +68,10 @@ const MarkdownHelp = ({helpText = 'Markdown styling is supported', markdownRefer
6668

6769
class ReactMde extends Component {
6870

71+
static propTypes = {
72+
commands: React.PropTypes.array
73+
}
74+
6975
/**
7076
*
7177
*/
@@ -88,22 +94,22 @@ class ReactMde extends Component {
8894
* @param {function} command
8995
* @memberOf ReactMde
9096
*/
91-
getCommandHandler(command) {
97+
getCommandHandler(commandFunction) {
9298
return function () {
9399
let {
94100
value: { text, selection },
95101
onChange
96102
} = this.props;
97103
let textarea = this.refs.textarea;
98104

99-
var newValue = command(text, getSelection(textarea));
100-
105+
var newValue = commandFunction(text, getSelection(textarea));
106+
101107
// let's select EVERYTHING and replace with the result of the command.
102108
// This will cause an 'inconvenience' which is: Ctrl + Z will select the whole
103109
// text. But this is the LEAST possible inconvenience. We can pretty much live
104110
// with it. I've tried everything in my reach, including reimplementing the textarea
105111
// history. That caused more problems than it solved.
106-
112+
107113
this.refs.textarea.focus();
108114
setSelection(this.refs.textarea, 0, this.refs.textarea.value.length);
109115
document.execCommand("insertText", false, newValue.text);
@@ -116,28 +122,32 @@ class ReactMde extends Component {
116122

117123
let {
118124
value: { text, selection },
119-
onChange
125+
onChange,
126+
commands
120127
} = this.props;
121128

122129
let html = this.converter.makeHtml(text) || '<p>&nbsp</p>';
123130

131+
let header = null;
132+
if (commands) {
133+
header = <div className="mde-header">
134+
{
135+
commands.map((cg, i) => {
136+
return <HeaderGroup key={i}>
137+
{
138+
cg.map((c, j) => {
139+
return <HeaderItem key={j} icon={c.icon} tooltip={c.tooltip} onClick={this.getCommandHandler(c.execute).bind(this)} />
140+
})
141+
}
142+
</HeaderGroup>
143+
})
144+
}
145+
</div>
146+
}
147+
124148
return (
125149
<div className="react-mde">
126-
<div className="mde-header">
127-
<HeaderGroup>
128-
<HeaderItem icon="bold" tooltip="Add bold text" onClick={this.getCommandHandler(ReactMdeCommands.makeBold).bind(this)} />
129-
<HeaderItem icon="italic" tooltip="Add italic text" onClick={this.getCommandHandler(ReactMdeCommands.makeItalic).bind(this)} />
130-
</HeaderGroup>
131-
<HeaderGroup>
132-
<HeaderItem icon="link" tooltip="Insert a link" onClick={this.getCommandHandler(ReactMdeCommands.makeLink).bind(this)} />
133-
<HeaderItem icon="quote-right" tooltip="Insert a quote" onClick={this.getCommandHandler(ReactMdeCommands.makeQuote).bind(this)} />
134-
<HeaderItem icon="picture-o" tooltip="Insert a picture" onClick={this.getCommandHandler(ReactMdeCommands.makeImage).bind(this)} />
135-
</HeaderGroup>
136-
<HeaderGroup>
137-
<HeaderItem icon="list-ul" tooltip="Add a bulleted list" onClick={this.getCommandHandler(ReactMdeCommands.makeUnorderedList).bind(this)} />
138-
<HeaderItem icon="list-ol" tooltip="Add a numbered list" onClick={this.getCommandHandler(ReactMdeCommands.makeOrderedList).bind(this)} />
139-
</HeaderGroup>
140-
</div>
150+
{header}
141151
<div className="mde-text">
142152
<textarea onChange={this.handleValueChange.bind(this)} value={text} ref="textarea" />
143153
</div>

src/ReactMdeCommands.js

Lines changed: 95 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -42,113 +42,120 @@ function makeList(text, selection, insertionBeforeEachLine) {
4242

4343
export default {
4444

45-
/**
46-
* Makes the text bold
47-
*
48-
* @param {any} text
49-
* @param {any} selection
50-
* @returns
51-
*/
52-
makeBold: function (text, selection) {
53-
if (text && text.length && selection[0] == selection[1]) {
54-
// the user is pointing to a word
55-
selection = getSurroundingWord(text, selection[0]).position;
56-
}
57-
// the user is selecting a word section
58-
var {newText, insertionLength} = insertText(text, '**', selection[0]);
59-
newText = insertText(newText, '**', selection[1] + insertionLength).newText;
60-
return {
61-
previousText: text,
62-
text: newText,
63-
selection: [selection[0] + insertionLength, selection[1] + insertionLength]
45+
makeBold: {
46+
icon: 'bold',
47+
tooltip: 'Add bold text',
48+
execute: function (text, selection) {
49+
if (text && text.length && selection[0] == selection[1]) {
50+
// the user is pointing to a word
51+
selection = getSurroundingWord(text, selection[0]).position;
52+
}
53+
// the user is selecting a word section
54+
var {newText, insertionLength} = insertText(text, '**', selection[0]);
55+
newText = insertText(newText, '**', selection[1] + insertionLength).newText;
56+
return {
57+
previousText: text,
58+
text: newText,
59+
selection: [selection[0] + insertionLength, selection[1] + insertionLength]
60+
}
6461
}
6562
},
6663

67-
/**
68-
* Makes the text italic
69-
*
70-
* @param {any} text
71-
* @param {any} selection
72-
* @returns
73-
*/
74-
makeItalic: function (text, selection) {
75-
if (text && text.length && selection[0] == selection[1]) {
76-
// the user is pointing to a word
77-
selection = getSurroundingWord(text, selection[0]).position;
78-
}
79-
// the user is selecting a word section
80-
var {newText, insertionLength} = insertText(text, '_', selection[0]);
81-
newText = insertText(newText, '_', selection[1] + insertionLength).newText;
82-
return {
83-
previousText: text,
84-
text: newText,
85-
selection: [selection[0] + insertionLength, selection[1] + insertionLength]
64+
makeItalic: {
65+
icon: 'italic',
66+
tooltip: 'Add italic text',
67+
execute: function (text, selection) {
68+
if (text && text.length && selection[0] == selection[1]) {
69+
// the user is pointing to a word
70+
selection = getSurroundingWord(text, selection[0]).position;
71+
}
72+
// the user is selecting a word section
73+
var {newText, insertionLength} = insertText(text, '_', selection[0]);
74+
newText = insertText(newText, '_', selection[1] + insertionLength).newText;
75+
return {
76+
previousText: text,
77+
text: newText,
78+
selection: [selection[0] + insertionLength, selection[1] + insertionLength]
79+
}
8680
}
8781
},
8882

89-
/**
90-
* Makes a link
91-
*
92-
* @param {any} text
93-
* @param {any} selection
94-
* @returns
95-
*/
96-
makeLink: function (text, selection) {
97-
var {newText, insertionLength} = insertText(text, '[', selection[0]);
98-
newText = insertText(newText, '](url)', selection[1] + insertionLength).newText;
99-
return {
100-
previousText: text,
101-
text: newText,
102-
selection: [selection[0] + insertionLength, selection[1] + insertionLength]
83+
makeLink: {
84+
icon: 'link',
85+
tooltip: 'Insert a link',
86+
execute: function (text, selection) {
87+
var {newText, insertionLength} = insertText(text, '[', selection[0]);
88+
newText = insertText(newText, '](url)', selection[1] + insertionLength).newText;
89+
return {
90+
previousText: text,
91+
text: newText,
92+
selection: [selection[0] + insertionLength, selection[1] + insertionLength]
93+
}
10394
}
10495
},
10596

106-
makeQuote: function (text, selection) {
107-
if (text && text.length && selection[0] == selection[1]) {
108-
// the user is pointing to a word
109-
selection = getSurroundingWord(text, selection[0]).position;
110-
}
111-
112-
let insertionBefore = '> ';
113-
if (selection[0] > 0) {
114-
let breaksNeeded = getBreaksNeededForEmptyLineBefore(text, selection[0]);
115-
insertionBefore = Array(breaksNeeded + 1).join("\n") + insertionBefore;
97+
makeQuote: {
98+
icon: 'quote-right',
99+
tooltip: 'Insert a quote',
100+
execute: function (text, selection) {
101+
if (text && text.length && selection[0] == selection[1]) {
102+
// the user is pointing to a word
103+
selection = getSurroundingWord(text, selection[0]).position;
104+
}
105+
106+
let insertionBefore = '> ';
107+
if (selection[0] > 0) {
108+
let breaksNeeded = getBreaksNeededForEmptyLineBefore(text, selection[0]);
109+
insertionBefore = Array(breaksNeeded + 1).join("\n") + insertionBefore;
110+
}
111+
112+
// the user is selecting a word section
113+
var {newText, insertionLength} = insertText(text, insertionBefore, selection[0]);
114+
newText = insertText(newText, '\n\n', selection[1] + insertionLength).newText;
115+
return {
116+
previousText: text,
117+
text: newText,
118+
selection: [selection[0] + insertionLength, selection[1] + insertionLength]
119+
}
116120
}
121+
},
117122

118-
// the user is selecting a word section
119-
var {newText, insertionLength} = insertText(text, insertionBefore, selection[0]);
120-
newText = insertText(newText, '\n\n', selection[1] + insertionLength).newText;
121-
return {
122-
previousText: text,
123-
text: newText,
124-
selection: [selection[0] + insertionLength, selection[1] + insertionLength]
123+
makeImage: {
124+
icon: 'picture-o',
125+
tooltip: 'Insert a picture',
126+
execute: function (text, selection) {
127+
var {newText, insertionLength} = insertText(text, '![', selection[0]);
128+
newText = insertText(newText, '](image-url)', selection[1] + insertionLength).newText;
129+
return {
130+
previousText: text,
131+
text: newText,
132+
selection: [selection[0] + insertionLength, selection[1] + insertionLength]
133+
}
125134
}
126135
},
127136

128-
129-
/**
130-
* Makes an image
131-
*
132-
* @param {any} text
133-
* @param {any} selection
134-
* @returns
135-
*/
136-
makeImage: function (text, selection) {
137-
var {newText, insertionLength} = insertText(text, '![', selection[0]);
138-
newText = insertText(newText, '](image-url)', selection[1] + insertionLength).newText;
139-
return {
140-
previousText: text,
141-
text: newText,
142-
selection: [selection[0] + insertionLength, selection[1] + insertionLength]
137+
makeUnorderedList: {
138+
icon: 'list-ul',
139+
tooltip: 'Add a bulleted list',
140+
execute: function (text, selection) {
141+
return makeList(text, selection, '- ');
143142
}
144143
},
145144

146-
makeUnorderedList: function (text, selection) {
147-
return makeList(text, selection, '- ');
145+
makeOrderedList: {
146+
icon: 'list-ol',
147+
tooltip: 'Add a numbered list',
148+
execute: function (text, selection) {
149+
return makeList(text, selection, (item, index) => `${index + 1}. `);
150+
}
148151
},
149152

150-
makeOrderedList: function (text, selection) {
151-
return makeList(text, selection, (item, index) => `${index + 1}. `);
153+
getDefaultCommands: function() {
154+
return [
155+
[this.makeBold, this.makeItalic],
156+
[this.makeLink, this.makeQuote, this.makeImage],
157+
[this.makeUnorderedList, this.makeOrderedList]
158+
]
152159
}
153160

154161
}

0 commit comments

Comments
 (0)