1
/*global require,module*/
3
var CodeMirror = require("codemirror");
4
require("codemirror/addon/edit/continuelist.js");
5
require("./codemirror/tablist");
6
require("codemirror/addon/display/fullscreen.js");
7
require("codemirror/mode/markdown/markdown.js");
8
require("codemirror/addon/mode/overlay.js");
9
require("codemirror/addon/display/placeholder.js");
10
require("codemirror/addon/selection/mark-selection.js");
11
require("codemirror/mode/gfm/gfm.js");
12
require("codemirror/mode/xml/xml.js");
13
var CodeMirrorSpellChecker = require("codemirror-spell-checker");
14
var marked = require("marked");
18
var isMac = /Mac/.test(navigator.platform);
20
// Mapping of actions that can be bound to keyboard shortcuts or toolbar buttons
22
"toggleBold": toggleBold,
23
"toggleItalic": toggleItalic,
25
"toggleHeadingSmaller": toggleHeadingSmaller,
26
"toggleHeadingBigger": toggleHeadingBigger,
27
"drawImage": drawImage,
28
"toggleBlockquote": toggleBlockquote,
29
"toggleOrderedList": toggleOrderedList,
30
"toggleUnorderedList": toggleUnorderedList,
31
"toggleCodeBlock": toggleCodeBlock,
32
"togglePreview": togglePreview,
33
"toggleStrikethrough": toggleStrikethrough,
34
"toggleHeading1": toggleHeading1,
35
"toggleHeading2": toggleHeading2,
36
"toggleHeading3": toggleHeading3,
37
"cleanBlock": cleanBlock,
38
"drawTable": drawTable,
39
"drawHorizontalRule": drawHorizontalRule,
42
"toggleSideBySide": toggleSideBySide,
43
"toggleFullScreen": toggleFullScreen
47
"toggleBold": "Cmd-B",
48
"toggleItalic": "Cmd-I",
50
"toggleHeadingSmaller": "Cmd-H",
51
"toggleHeadingBigger": "Shift-Cmd-H",
52
"cleanBlock": "Cmd-E",
53
"drawImage": "Cmd-Alt-I",
54
"toggleBlockquote": "Cmd-'",
55
"toggleOrderedList": "Cmd-Alt-L",
56
"toggleUnorderedList": "Cmd-L",
57
"toggleCodeBlock": "Cmd-Alt-C",
58
"togglePreview": "Cmd-P",
59
"toggleSideBySide": "F9",
60
"toggleFullScreen": "F11"
63
var getBindingName = function(f) {
64
for(var key in bindings) {
65
if(bindings[key] === f) {
72
var isMobile = function() {
75
if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) check = true;
76
})(navigator.userAgent || navigator.vendor || window.opera);
82
* Fix shortcut. Mac use Command, others use Ctrl.
84
function fixShortcut(name) {
86
name = name.replace("Ctrl", "Cmd");
88
name = name.replace("Cmd", "Ctrl");
95
* Create icon element for toolbar.
97
function createIcon(options, enableTooltips, shortcuts) {
98
options = options || {};
99
var el = document.createElement("a");
100
enableTooltips = (enableTooltips == undefined) ? true : enableTooltips;
102
if(options.title && enableTooltips) {
103
el.title = createTootlip(options.title, options.action, shortcuts);
106
el.title = el.title.replace("Ctrl", "⌘");
107
el.title = el.title.replace("Alt", "⌥");
112
el.className = options.className;
116
function createSep() {
117
var el = document.createElement("i");
118
el.className = "separator";
123
function createTootlip(title, action, shortcuts) {
128
actionName = getBindingName(action);
129
if(shortcuts[actionName]) {
130
tooltip += " (" + fixShortcut(shortcuts[actionName]) + ")";
138
* The state of CodeMirror at the given position.
140
function getState(cm, pos) {
141
pos = pos || cm.getCursor("start");
142
var stat = cm.getTokenAt(pos);
143
if(!stat.type) return {};
145
var types = stat.type.split(" ");
149
for(var i = 0; i < types.length; i++) {
151
if(data === "strong") {
153
} else if(data === "variable-2") {
154
text = cm.getLine(pos.line);
155
if(/^\s*\d+\.\s/.test(text)) {
156
ret["ordered-list"] = true;
158
ret["unordered-list"] = true;
160
} else if(data === "atom") {
162
} else if(data === "em") {
164
} else if(data === "quote") {
166
} else if(data === "strikethrough") {
167
ret.strikethrough = true;
168
} else if(data === "comment") {
170
} else if(data === "link") {
172
} else if(data === "tag") {
174
} else if(data.match(/^header(\-[1-6])?$/)) {
175
ret[data.replace("header", "heading")] = true;
182
// Saved overflow setting
183
var saved_overflow = "";
186
* Toggle full screen of the editor.
188
function toggleFullScreen(editor) {
190
var cm = editor.codemirror;
191
cm.setOption("fullScreen", !cm.getOption("fullScreen"));
194
// Prevent scrolling on body during fullscreen active
195
if(cm.getOption("fullScreen")) {
196
saved_overflow = document.body.style.overflow;
197
document.body.style.overflow = "hidden";
199
document.body.style.overflow = saved_overflow;
203
// Update toolbar class
204
var wrap = cm.getWrapperElement();
206
if(!/fullscreen/.test(wrap.previousSibling.className)) {
207
wrap.previousSibling.className += " fullscreen";
209
wrap.previousSibling.className = wrap.previousSibling.className.replace(/\s*fullscreen\b/, "");
213
// Update toolbar button
214
var toolbarButton = editor.toolbarElements.fullscreen;
216
if(!/active/.test(toolbarButton.className)) {
217
toolbarButton.className += " active";
219
toolbarButton.className = toolbarButton.className.replace(/\s*active\s*/g, "");
223
// Hide side by side if needed
224
var sidebyside = cm.getWrapperElement().nextSibling;
225
if(/editor-preview-active-side/.test(sidebyside.className))
226
toggleSideBySide(editor);
231
* Action for toggling bold.
233
function toggleBold(editor) {
234
_toggleBlock(editor, "bold", editor.options.blockStyles.bold);
239
* Action for toggling italic.
241
function toggleItalic(editor) {
242
_toggleBlock(editor, "italic", editor.options.blockStyles.italic);
247
* Action for toggling strikethrough.
249
function toggleStrikethrough(editor) {
250
_toggleBlock(editor, "strikethrough", "~~");
254
* Action for toggling code block.
256
function toggleCodeBlock(editor) {
257
var fenceCharsToInsert = editor.options.blockStyles.code;
259
function fencing_line(line) {
260
/* return true, if this is a ``` or ~~~ line */
261
if(typeof line !== "object") {
262
throw "fencing_line() takes a 'line' object (not a line number, or line text). Got: " + typeof line + ": " + line;
264
return line.styles && line.styles[2] && line.styles[2].indexOf("formatting-code-block") !== -1;
267
function token_state(token) {
268
// base goes an extra level deep when mode backdrops are used, e.g. spellchecker on
269
return token.state.base.base || token.state.base;
272
function code_type(cm, line_num, line, firstTok, lastTok) {
274
* Return "single", "indented", "fenced" or false
276
* cm and line_num are required. Others are optional for efficiency
277
* To check in the middle of a line, pass in firstTok yourself.
279
line = line || cm.getLineHandle(line_num);
280
firstTok = firstTok || cm.getTokenAt({
284
lastTok = lastTok || (!!line.text && cm.getTokenAt({
286
ch: line.text.length - 1
288
var types = firstTok.type ? firstTok.type.split(" ") : [];
289
if(lastTok && token_state(lastTok).indentedCode) {
290
// have to check last char, since first chars of first line aren"t marked as indented
292
} else if(types.indexOf("comment") === -1) {
293
// has to be after "indented" check, since first chars of first indented line aren"t marked as such
295
} else if(token_state(firstTok).fencedChars || token_state(lastTok).fencedChars || fencing_line(line)) {
302
function insertFencingAtSelection(cm, cur_start, cur_end, fenceCharsToInsert) {
303
var start_line_sel = cur_start.line + 1,
304
end_line_sel = cur_end.line + 1,
305
sel_multi = cur_start.line !== cur_end.line,
306
repl_start = fenceCharsToInsert + "\n",
307
repl_end = "\n" + fenceCharsToInsert;
311
// handle last char including \n or not
312
if(sel_multi && cur_end.ch === 0) {
313
repl_end = fenceCharsToInsert + "\n";
316
_replaceSelection(cm, false, [repl_start, repl_end]);
318
line: start_line_sel,
326
var cm = editor.codemirror,
327
cur_start = cm.getCursor("start"),
328
cur_end = cm.getCursor("end"),
329
tok = cm.getTokenAt({
330
line: cur_start.line,
331
ch: cur_start.ch || 1
332
}), // avoid ch 0 which is a cursor pos but not token
333
line = cm.getLineHandle(cur_start.line),
334
is_code = code_type(cm, cur_start.line, line, tok);
335
var block_start, block_end, lineCount;
337
if(is_code === "single") {
338
// similar to some SimpleMDE _toggleBlock logic
339
var start = line.text.slice(0, cur_start.ch).replace("`", ""),
340
end = line.text.slice(cur_start.ch).replace("`", "");
341
cm.replaceRange(start + end, {
342
line: cur_start.line,
345
line: cur_start.line,
349
if(cur_start !== cur_end) {
352
cm.setSelection(cur_start, cur_end);
354
} else if(is_code === "fenced") {
355
if(cur_start.line !== cur_end.line || cur_start.ch !== cur_end.ch) {
358
// find the fenced line so we know what type it is (tilde, backticks, number of them)
359
for(block_start = cur_start.line; block_start >= 0; block_start--) {
360
line = cm.getLineHandle(block_start);
361
if(fencing_line(line)) {
365
var fencedTok = cm.getTokenAt({
369
var fence_chars = token_state(fencedTok).fencedChars;
370
var start_text, start_line;
371
var end_text, end_line;
372
// check for selection going up against fenced lines, in which case we don't want to add more fencing
373
if(fencing_line(cm.getLineHandle(cur_start.line))) {
375
start_line = cur_start.line;
376
} else if(fencing_line(cm.getLineHandle(cur_start.line - 1))) {
378
start_line = cur_start.line - 1;
380
start_text = fence_chars + "\n";
381
start_line = cur_start.line;
383
if(fencing_line(cm.getLineHandle(cur_end.line))) {
385
end_line = cur_end.line;
386
if(cur_end.ch === 0) {
389
} else if(cur_end.ch !== 0 && fencing_line(cm.getLineHandle(cur_end.line + 1))) {
391
end_line = cur_end.line + 1;
393
end_text = fence_chars + "\n";
394
end_line = cur_end.line + 1;
396
if(cur_end.ch === 0) {
397
// full last line selected, putting cursor at beginning of next
400
cm.operation(function() {
401
// end line first, so that line numbers don't change
402
cm.replaceRange(end_text, {
406
line: end_line + (end_text ? 0 : 1),
409
cm.replaceRange(start_text, {
413
line: start_line + (start_text ? 0 : 1),
418
line: start_line + (start_text ? 1 : 0),
421
line: end_line + (start_text ? 1 : -1),
426
// no selection, search for ends of this fenced block
427
var search_from = cur_start.line;
428
if(fencing_line(cm.getLineHandle(cur_start.line))) { // gets a little tricky if cursor is right on a fenced line
429
if(code_type(cm, cur_start.line + 1) === "fenced") {
430
block_start = cur_start.line;
431
search_from = cur_start.line + 1; // for searching for "end"
433
block_end = cur_start.line;
434
search_from = cur_start.line - 1; // for searching for "start"
437
if(block_start === undefined) {
438
for(block_start = search_from; block_start >= 0; block_start--) {
439
line = cm.getLineHandle(block_start);
440
if(fencing_line(line)) {
445
if(block_end === undefined) {
446
lineCount = cm.lineCount();
447
for(block_end = search_from; block_end < lineCount; block_end++) {
448
line = cm.getLineHandle(block_end);
449
if(fencing_line(line)) {
454
cm.operation(function() {
455
cm.replaceRange("", {
459
line: block_start + 1,
462
cm.replaceRange("", {
472
} else if(is_code === "indented") {
473
if(cur_start.line !== cur_end.line || cur_start.ch !== cur_end.ch) {
475
block_start = cur_start.line;
476
block_end = cur_end.line;
477
if(cur_end.ch === 0) {
481
// no selection, search for ends of this indented block
482
for(block_start = cur_start.line; block_start >= 0; block_start--) {
483
line = cm.getLineHandle(block_start);
484
if(line.text.match(/^\s*$/)) {
485
// empty or all whitespace - keep going
488
if(code_type(cm, block_start, line) !== "indented") {
494
lineCount = cm.lineCount();
495
for(block_end = cur_start.line; block_end < lineCount; block_end++) {
496
line = cm.getLineHandle(block_end);
497
if(line.text.match(/^\s*$/)) {
498
// empty or all whitespace - keep going
501
if(code_type(cm, block_end, line) !== "indented") {
508
// if we are going to un-indent based on a selected set of lines, and the next line is indented too, we need to
509
// insert a blank line so that the next line(s) continue to be indented code
510
var next_line = cm.getLineHandle(block_end + 1),
511
next_line_last_tok = next_line && cm.getTokenAt({
513
ch: next_line.text.length - 1
515
next_line_indented = next_line_last_tok && token_state(next_line_last_tok).indentedCode;
516
if(next_line_indented) {
517
cm.replaceRange("\n", {
523
for(var i = block_start; i <= block_end; i++) {
524
cm.indentLine(i, "subtract"); // TODO: this doesn't get tracked in the history, so can't be undone :(
528
// insert code formatting
529
var no_sel_and_starting_of_line = (cur_start.line === cur_end.line && cur_start.ch === cur_end.ch && cur_start.ch === 0);
530
var sel_multi = cur_start.line !== cur_end.line;
531
if(no_sel_and_starting_of_line || sel_multi) {
532
insertFencingAtSelection(cm, cur_start, cur_end, fenceCharsToInsert);
534
_replaceSelection(cm, false, ["`", "`"]);
540
* Action for toggling blockquote.
542
function toggleBlockquote(editor) {
543
var cm = editor.codemirror;
544
_toggleLine(cm, "quote");
548
* Action for toggling heading size: normal -> h1 -> h2 -> h3 -> h4 -> h5 -> h6 -> normal
550
function toggleHeadingSmaller(editor) {
551
var cm = editor.codemirror;
552
_toggleHeading(cm, "smaller");
556
* Action for toggling heading size: normal -> h6 -> h5 -> h4 -> h3 -> h2 -> h1 -> normal
558
function toggleHeadingBigger(editor) {
559
var cm = editor.codemirror;
560
_toggleHeading(cm, "bigger");
564
* Action for toggling heading size 1
566
function toggleHeading1(editor) {
567
var cm = editor.codemirror;
568
_toggleHeading(cm, undefined, 1);
572
* Action for toggling heading size 2
574
function toggleHeading2(editor) {
575
var cm = editor.codemirror;
576
_toggleHeading(cm, undefined, 2);
580
* Action for toggling heading size 3
582
function toggleHeading3(editor) {
583
var cm = editor.codemirror;
584
_toggleHeading(cm, undefined, 3);
589
* Action for toggling ul.
591
function toggleUnorderedList(editor) {
592
var cm = editor.codemirror;
593
_toggleLine(cm, "unordered-list");
598
* Action for toggling ol.
600
function toggleOrderedList(editor) {
601
var cm = editor.codemirror;
602
_toggleLine(cm, "ordered-list");
606
* Action for clean block (remove headline, list, blockquote code, markers)
608
function cleanBlock(editor) {
609
var cm = editor.codemirror;
614
* Action for drawing a link.
616
function drawLink(editor) {
617
var cm = editor.codemirror;
618
var stat = getState(cm);
619
var options = editor.options;
621
if(options.promptURLs) {
622
url = prompt(options.promptTexts.link);
627
_replaceSelection(cm, stat.link, options.insertTexts.link, url);
631
* Action for drawing an img.
633
function drawImage(editor) {
634
var cm = editor.codemirror;
635
var stat = getState(cm);
636
var options = editor.options;
638
if(options.promptURLs) {
639
url = prompt(options.promptTexts.image);
644
_replaceSelection(cm, stat.image, options.insertTexts.image, url);
648
* Action for drawing a table.
650
function drawTable(editor) {
651
var cm = editor.codemirror;
652
var stat = getState(cm);
653
var options = editor.options;
654
_replaceSelection(cm, stat.table, options.insertTexts.table);
658
* Action for drawing a horizontal rule.
660
function drawHorizontalRule(editor) {
661
var cm = editor.codemirror;
662
var stat = getState(cm);
663
var options = editor.options;
664
_replaceSelection(cm, stat.image, options.insertTexts.horizontalRule);
671
function undo(editor) {
672
var cm = editor.codemirror;
681
function redo(editor) {
682
var cm = editor.codemirror;
689
* Toggle side by side preview
691
function toggleSideBySide(editor) {
692
var cm = editor.codemirror;
693
var wrapper = cm.getWrapperElement();
694
var preview = wrapper.nextSibling;
695
var toolbarButton = editor.toolbarElements["side-by-side"];
696
var useSideBySideListener = false;
697
if(/editor-preview-active-side/.test(preview.className)) {
698
preview.className = preview.className.replace(
699
/\s*editor-preview-active-side\s*/g, ""
701
toolbarButton.className = toolbarButton.className.replace(/\s*active\s*/g, "");
702
wrapper.className = wrapper.className.replace(/\s*CodeMirror-sided\s*/g, " ");
704
// When the preview button is clicked for the first time,
705
// give some time for the transition from editor.css to fire and the view to slide from right to left,
706
// instead of just appearing.
707
setTimeout(function() {
708
if(!cm.getOption("fullScreen"))
709
toggleFullScreen(editor);
710
preview.className += " editor-preview-active-side";
712
toolbarButton.className += " active";
713
wrapper.className += " CodeMirror-sided";
714
useSideBySideListener = true;
717
// Hide normal preview if active
718
var previewNormal = wrapper.lastChild;
719
if(/editor-preview-active/.test(previewNormal.className)) {
720
previewNormal.className = previewNormal.className.replace(
721
/\s*editor-preview-active\s*/g, ""
723
var toolbar = editor.toolbarElements.preview;
724
var toolbar_div = wrapper.previousSibling;
725
toolbar.className = toolbar.className.replace(/\s*active\s*/g, "");
726
toolbar_div.className = toolbar_div.className.replace(/\s*disabled-for-preview*/g, "");
729
var sideBySideRenderingFunction = function() {
730
preview.innerHTML = editor.options.previewRender(editor.value(), preview);
733
if(!cm.sideBySideRenderingFunction) {
734
cm.sideBySideRenderingFunction = sideBySideRenderingFunction;
737
if(useSideBySideListener) {
738
preview.innerHTML = editor.options.previewRender(editor.value(), preview);
739
cm.on("update", cm.sideBySideRenderingFunction);
741
cm.off("update", cm.sideBySideRenderingFunction);
744
// Refresh to fix selection being off (#309)
752
function togglePreview(editor) {
753
var cm = editor.codemirror;
754
var wrapper = cm.getWrapperElement();
755
var toolbar_div = wrapper.previousSibling;
756
var toolbar = editor.options.toolbar ? editor.toolbarElements.preview : false;
757
var preview = wrapper.lastChild;
758
if(!preview || !/editor-preview/.test(preview.className)) {
759
preview = document.createElement("div");
760
preview.className = "editor-preview";
761
wrapper.appendChild(preview);
763
if(/editor-preview-active/.test(preview.className)) {
764
preview.className = preview.className.replace(
765
/\s*editor-preview-active\s*/g, ""
768
toolbar.className = toolbar.className.replace(/\s*active\s*/g, "");
769
toolbar_div.className = toolbar_div.className.replace(/\s*disabled-for-preview*/g, "");
772
// When the preview button is clicked for the first time,
773
// give some time for the transition from editor.css to fire and the view to slide from right to left,
774
// instead of just appearing.
775
setTimeout(function() {
776
preview.className += " editor-preview-active";
779
toolbar.className += " active";
780
toolbar_div.className += " disabled-for-preview";
783
preview.innerHTML = editor.options.previewRender(editor.value(), preview);
785
// Turn off side by side if needed
786
var sidebyside = cm.getWrapperElement().nextSibling;
787
if(/editor-preview-active-side/.test(sidebyside.className))
788
toggleSideBySide(editor);
791
function _replaceSelection(cm, active, startEnd, url) {
792
if(/editor-preview-active/.test(cm.getWrapperElement().lastChild.className))
796
var start = startEnd[0];
797
var end = startEnd[1];
798
var startPoint = cm.getCursor("start");
799
var endPoint = cm.getCursor("end");
801
end = end.replace("#url#", url);
804
text = cm.getLine(startPoint.line);
805
start = text.slice(0, startPoint.ch);
806
end = text.slice(startPoint.ch);
807
cm.replaceRange(start + end, {
808
line: startPoint.line,
812
text = cm.getSelection();
813
cm.replaceSelection(start + text + end);
815
startPoint.ch += start.length;
816
if(startPoint !== endPoint) {
817
endPoint.ch += start.length;
820
cm.setSelection(startPoint, endPoint);
825
function _toggleHeading(cm, direction, size) {
826
if(/editor-preview-active/.test(cm.getWrapperElement().lastChild.className))
829
var startPoint = cm.getCursor("start");
830
var endPoint = cm.getCursor("end");
831
for(var i = startPoint.line; i <= endPoint.line; i++) {
833
var text = cm.getLine(i);
834
var currHeadingLevel = text.search(/[^#]/);
836
if(direction !== undefined) {
837
if(currHeadingLevel <= 0) {
838
if(direction == "bigger") {
839
text = "###### " + text;
843
} else if(currHeadingLevel == 6 && direction == "smaller") {
844
text = text.substr(7);
845
} else if(currHeadingLevel == 1 && direction == "bigger") {
846
text = text.substr(2);
848
if(direction == "bigger") {
849
text = text.substr(1);
856
if(currHeadingLevel <= 0) {
858
} else if(currHeadingLevel == size) {
859
text = text.substr(currHeadingLevel + 1);
861
text = "# " + text.substr(currHeadingLevel + 1);
863
} else if(size == 2) {
864
if(currHeadingLevel <= 0) {
866
} else if(currHeadingLevel == size) {
867
text = text.substr(currHeadingLevel + 1);
869
text = "## " + text.substr(currHeadingLevel + 1);
872
if(currHeadingLevel <= 0) {
873
text = "### " + text;
874
} else if(currHeadingLevel == size) {
875
text = text.substr(currHeadingLevel + 1);
877
text = "### " + text.substr(currHeadingLevel + 1);
882
cm.replaceRange(text, {
895
function _toggleLine(cm, name) {
896
if(/editor-preview-active/.test(cm.getWrapperElement().lastChild.className))
899
var stat = getState(cm);
900
var startPoint = cm.getCursor("start");
901
var endPoint = cm.getCursor("end");
903
"quote": /^(\s*)\>\s+/,
904
"unordered-list": /^(\s*)(\*|\-|\+)\s+/,
905
"ordered-list": /^(\s*)\d+\.\s+/
909
"unordered-list": "* ",
910
"ordered-list": "1. "
912
for(var i = startPoint.line; i <= endPoint.line; i++) {
914
var text = cm.getLine(i);
916
text = text.replace(repl[name], "$1");
918
text = map[name] + text;
920
cm.replaceRange(text, {
932
function _toggleBlock(editor, type, start_chars, end_chars) {
933
if(/editor-preview-active/.test(editor.codemirror.getWrapperElement().lastChild.className))
936
end_chars = (typeof end_chars === "undefined") ? start_chars : end_chars;
937
var cm = editor.codemirror;
938
var stat = getState(cm);
941
var start = start_chars;
944
var startPoint = cm.getCursor("start");
945
var endPoint = cm.getCursor("end");
948
text = cm.getLine(startPoint.line);
949
start = text.slice(0, startPoint.ch);
950
end = text.slice(startPoint.ch);
952
start = start.replace(/(\*\*|__)(?![\s\S]*(\*\*|__))/, "");
953
end = end.replace(/(\*\*|__)/, "");
954
} else if(type == "italic") {
955
start = start.replace(/(\*|_)(?![\s\S]*(\*|_))/, "");
956
end = end.replace(/(\*|_)/, "");
957
} else if(type == "strikethrough") {
958
start = start.replace(/(\*\*|~~)(?![\s\S]*(\*\*|~~))/, "");
959
end = end.replace(/(\*\*|~~)/, "");
961
cm.replaceRange(start + end, {
962
line: startPoint.line,
965
line: startPoint.line,
969
if(type == "bold" || type == "strikethrough") {
971
if(startPoint !== endPoint) {
974
} else if(type == "italic") {
976
if(startPoint !== endPoint) {
981
text = cm.getSelection();
983
text = text.split("**").join("");
984
text = text.split("__").join("");
985
} else if(type == "italic") {
986
text = text.split("*").join("");
987
text = text.split("_").join("");
988
} else if(type == "strikethrough") {
989
text = text.split("~~").join("");
991
cm.replaceSelection(start + text + end);
993
startPoint.ch += start_chars.length;
994
endPoint.ch = startPoint.ch + text.length;
997
cm.setSelection(startPoint, endPoint);
1001
function _cleanBlock(cm) {
1002
if(/editor-preview-active/.test(cm.getWrapperElement().lastChild.className))
1005
var startPoint = cm.getCursor("start");
1006
var endPoint = cm.getCursor("end");
1009
for(var line = startPoint.line; line <= endPoint.line; line++) {
1010
text = cm.getLine(line);
1011
text = text.replace(/^[ ]*([# ]+|\*|\-|[> ]+|[0-9]+(.|\)))[ ]*/, "");
1013
cm.replaceRange(text, {
1023
// Merge the properties of one object into another.
1024
function _mergeProperties(target, source) {
1025
for(var property in source) {
1026
if(source.hasOwnProperty(property)) {
1027
if(source[property] instanceof Array) {
1028
target[property] = source[property].concat(target[property] instanceof Array ? target[property] : []);
1030
source[property] !== null &&
1031
typeof source[property] === "object" &&
1032
source[property].constructor === Object
1034
target[property] = _mergeProperties(target[property] || {}, source[property]);
1036
target[property] = source[property];
1044
// Merge an arbitrary number of objects into one.
1045
function extend(target) {
1046
for(var i = 1; i < arguments.length; i++) {
1047
target = _mergeProperties(target, arguments[i]);
1053
/* The right word count in respect for CJK. */
1054
function wordCount(data) {
1055
var pattern = /[a-zA-Z0-9_\u0392-\u03c9\u0410-\u04F9]+|[\u4E00-\u9FFF\u3400-\u4dbf\uf900-\ufaff\u3040-\u309f\uac00-\ud7af]+/g;
1056
var m = data.match(pattern);
1058
if(m === null) return count;
1059
for(var i = 0; i < m.length; i++) {
1060
if(m[i].charCodeAt(0) >= 0x4E00) {
1061
count += m[i].length;
1069
var toolbarBuiltInButtons = {
1073
className: "fa fa-bold",
1079
action: toggleItalic,
1080
className: "fa fa-italic",
1085
name: "strikethrough",
1086
action: toggleStrikethrough,
1087
className: "fa fa-strikethrough",
1088
title: "Strikethrough"
1092
action: toggleHeadingSmaller,
1093
className: "fa fa-header",
1097
"heading-smaller": {
1098
name: "heading-smaller",
1099
action: toggleHeadingSmaller,
1100
className: "fa fa-header fa-header-x fa-header-smaller",
1101
title: "Smaller Heading"
1104
name: "heading-bigger",
1105
action: toggleHeadingBigger,
1106
className: "fa fa-header fa-header-x fa-header-bigger",
1107
title: "Bigger Heading"
1111
action: toggleHeading1,
1112
className: "fa fa-header fa-header-x fa-header-1",
1113
title: "Big Heading"
1117
action: toggleHeading2,
1118
className: "fa fa-header fa-header-x fa-header-2",
1119
title: "Medium Heading"
1123
action: toggleHeading3,
1124
className: "fa fa-header fa-header-x fa-header-3",
1125
title: "Small Heading"
1132
action: toggleCodeBlock,
1133
className: "fa fa-code",
1138
action: toggleBlockquote,
1139
className: "fa fa-quote-left",
1144
name: "unordered-list",
1145
action: toggleUnorderedList,
1146
className: "fa fa-list-ul",
1147
title: "Generic List",
1151
name: "ordered-list",
1152
action: toggleOrderedList,
1153
className: "fa fa-list-ol",
1154
title: "Numbered List",
1158
name: "clean-block",
1160
className: "fa fa-eraser fa-clean-block",
1161
title: "Clean block"
1169
className: "fa fa-link",
1170
title: "Create Link",
1176
className: "fa fa-picture-o",
1177
title: "Insert Image",
1183
className: "fa fa-table",
1184
title: "Insert Table"
1186
"horizontal-rule": {
1187
name: "horizontal-rule",
1188
action: drawHorizontalRule,
1189
className: "fa fa-minus",
1190
title: "Insert Horizontal Line"
1197
action: togglePreview,
1198
className: "fa fa-eye no-disable",
1199
title: "Toggle Preview",
1203
name: "side-by-side",
1204
action: toggleSideBySide,
1205
className: "fa fa-columns no-disable no-mobile",
1206
title: "Toggle Side by Side",
1211
action: toggleFullScreen,
1212
className: "fa fa-arrows-alt no-disable no-mobile",
1213
title: "Toggle Fullscreen",
1221
action: "https://simplemde.com/markdown-guide",
1222
className: "fa fa-question-circle",
1223
title: "Markdown Guide",
1232
className: "fa fa-undo no-disable",
1238
className: "fa fa-repeat no-disable",
1244
link: ["[", "](#url#)"],
1245
image: ["![](", "#url#)"],
1246
table: ["", "\n\n| Column 1 | Column 2 | Column 3 |\n| -------- | -------- | -------- |\n| Text | Text | Text |\n\n"],
1247
horizontalRule: ["", "\n\n-----\n\n"]
1251
link: "URL for the link:",
1252
image: "URL of the image:"
1262
* Interface of SimpleMDE.
1264
function SimpleMDE(options) {
1265
// Handle options parameter
1266
options = options || {};
1269
// Used later to refer to it"s parent
1270
options.parent = this;
1273
// Check if Font Awesome needs to be auto downloaded
1274
var autoDownloadFA = true;
1276
if(options.autoDownloadFontAwesome === false) {
1277
autoDownloadFA = false;
1280
if(options.autoDownloadFontAwesome !== true) {
1281
var styleSheets = document.styleSheets;
1282
for(var i = 0; i < styleSheets.length; i++) {
1283
if(!styleSheets[i].href)
1286
if(styleSheets[i].href.indexOf("//maxcdn.bootstrapcdn.com/font-awesome/") > -1) {
1287
autoDownloadFA = false;
1292
if(autoDownloadFA) {
1293
var link = document.createElement("link");
1294
link.rel = "stylesheet";
1295
link.href = "https://maxcdn.bootstrapcdn.com/font-awesome/latest/css/font-awesome.min.css";
1296
document.getElementsByTagName("head")[0].appendChild(link);
1300
// Find the textarea to use
1301
if(options.element) {
1302
this.element = options.element;
1303
} else if(options.element === null) {
1304
// This means that the element option was specified, but no element was found
1305
console.log("SimpleMDE: Error. No element was found.");
1311
if(options.toolbar === undefined) {
1313
options.toolbar = [];
1316
// Loop over the built in buttons, to get the preferred order
1317
for(var key in toolbarBuiltInButtons) {
1318
if(toolbarBuiltInButtons.hasOwnProperty(key)) {
1319
if(key.indexOf("separator-") != -1) {
1320
options.toolbar.push("|");
1323
if(toolbarBuiltInButtons[key].default === true || (options.showIcons && options.showIcons.constructor === Array && options.showIcons.indexOf(key) != -1)) {
1324
options.toolbar.push(key);
1331
// Handle status bar
1332
if(!options.hasOwnProperty("status")) {
1333
options.status = ["autosave", "lines", "words", "cursor"];
1337
// Add default preview rendering function
1338
if(!options.previewRender) {
1339
options.previewRender = function(plainText) {
1340
// Note: "this" refers to the options object
1341
return this.parent.markdown(plainText);
1346
// Set default options for parsing config
1347
options.parsingConfig = extend({
1348
highlightFormatting: true // needed for toggleCodeBlock to detect types of code
1349
}, options.parsingConfig || {});
1352
// Merging the insertTexts, with the given options
1353
options.insertTexts = extend({}, insertTexts, options.insertTexts || {});
1356
// Merging the promptTexts, with the given options
1357
options.promptTexts = promptTexts;
1360
// Merging the blockStyles, with the given options
1361
options.blockStyles = extend({}, blockStyles, options.blockStyles || {});
1364
// Merging the shortcuts, with the given options
1365
options.shortcuts = extend({}, shortcuts, options.shortcuts || {});
1368
// Change unique_id to uniqueId for backwards compatibility
1369
if(options.autosave != undefined && options.autosave.unique_id != undefined && options.autosave.unique_id != "")
1370
options.autosave.uniqueId = options.autosave.unique_id;
1373
// Update this options
1374
this.options = options;
1381
// The codemirror component is only available after rendering
1382
// so, the setter for the initialValue can only run after
1383
// the element has been rendered
1384
if(options.initialValue && (!this.options.autosave || this.options.autosave.foundSavedValue !== true)) {
1385
this.value(options.initialValue);
1390
* Default markdown render.
1392
SimpleMDE.prototype.markdown = function(text) {
1395
var markedOptions = {};
1399
if(this.options && this.options.renderingConfig && this.options.renderingConfig.singleLineBreaks === false) {
1400
markedOptions.breaks = false;
1402
markedOptions.breaks = true;
1405
if(this.options && this.options.renderingConfig && this.options.renderingConfig.codeSyntaxHighlighting === true && window.hljs) {
1406
markedOptions.highlight = function(code) {
1407
return window.hljs.highlightAuto(code).value;
1413
marked.setOptions(markedOptions);
1417
return marked(text);
1422
* Render editor to the given element.
1424
SimpleMDE.prototype.render = function(el) {
1426
el = this.element || document.getElementsByTagName("textarea")[0];
1429
if(this._rendered && this._rendered === el) {
1430
// Already rendered.
1435
var options = this.options;
1440
for(var key in options.shortcuts) {
1441
// null stands for "do not bind this command"
1442
if(options.shortcuts[key] !== null && bindings[key] !== null) {
1444
keyMaps[fixShortcut(options.shortcuts[key])] = function() {
1445
bindings[key](self);
1451
keyMaps["Enter"] = "newlineAndIndentContinueMarkdownList";
1452
keyMaps["Tab"] = "tabAndIndentMarkdownList";
1453
keyMaps["Shift-Tab"] = "shiftTabAndUnindentMarkdownList";
1454
keyMaps["Esc"] = function(cm) {
1455
if(cm.getOption("fullScreen")) toggleFullScreen(self);
1458
document.addEventListener("keydown", function(e) {
1459
e = e || window.event;
1461
if(e.keyCode == 27) {
1462
if(self.codemirror.getOption("fullScreen")) toggleFullScreen(self);
1467
if(options.spellChecker !== false) {
1468
mode = "spell-checker";
1469
backdrop = options.parsingConfig;
1470
backdrop.name = "gfm";
1471
backdrop.gitHubSpice = false;
1473
CodeMirrorSpellChecker({
1474
codeMirrorInstance: CodeMirror
1477
mode = options.parsingConfig;
1479
mode.gitHubSpice = false;
1482
this.codemirror = CodeMirror.fromTextArea(el, {
1486
tabSize: (options.tabSize != undefined) ? options.tabSize : 2,
1487
indentUnit: (options.tabSize != undefined) ? options.tabSize : 2,
1488
indentWithTabs: (options.indentWithTabs === false) ? false : true,
1490
autofocus: (options.autofocus === true) ? true : false,
1492
lineWrapping: (options.lineWrapping === false) ? false : true,
1493
allowDropFileTypes: ["text/plain"],
1494
placeholder: options.placeholder || el.getAttribute("placeholder") || "",
1495
styleSelectedText: (options.styleSelectedText != undefined) ? options.styleSelectedText : true
1498
if(options.forceSync === true) {
1499
var cm = this.codemirror;
1500
cm.on("change", function() {
1507
if(options.toolbar !== false) {
1508
this.gui.toolbar = this.createToolbar();
1510
if(options.status !== false) {
1511
this.gui.statusbar = this.createStatusbar();
1513
if(options.autosave != undefined && options.autosave.enabled === true) {
1517
this.gui.sideBySide = this.createSideBySide();
1519
this._rendered = this.element;
1522
// Fixes CodeMirror bug (#344)
1523
var temp_cm = this.codemirror;
1524
setTimeout(function() {
1526
}.bind(temp_cm), 0);
1529
// Safari, in Private Browsing Mode, looks like it supports localStorage but all calls to setItem throw QuotaExceededError. We're going to detect this and set a variable accordingly.
1530
function isLocalStorageAvailable() {
1531
if(typeof localStorage === "object") {
1533
localStorage.setItem("smde_localStorage", 1);
1534
localStorage.removeItem("smde_localStorage");
1545
SimpleMDE.prototype.autosave = function() {
1546
if(isLocalStorageAvailable()) {
1547
var simplemde = this;
1549
if(this.options.autosave.uniqueId == undefined || this.options.autosave.uniqueId == "") {
1550
console.log("SimpleMDE: You must set a uniqueId to use the autosave feature");
1554
if(simplemde.element.form != null && simplemde.element.form != undefined) {
1555
simplemde.element.form.addEventListener("submit", function() {
1556
localStorage.removeItem("smde_" + simplemde.options.autosave.uniqueId);
1560
if(this.options.autosave.loaded !== true) {
1561
if(typeof localStorage.getItem("smde_" + this.options.autosave.uniqueId) == "string" && localStorage.getItem("smde_" + this.options.autosave.uniqueId) != "") {
1562
this.codemirror.setValue(localStorage.getItem("smde_" + this.options.autosave.uniqueId));
1563
this.options.autosave.foundSavedValue = true;
1566
this.options.autosave.loaded = true;
1569
localStorage.setItem("smde_" + this.options.autosave.uniqueId, simplemde.value());
1571
var el = document.getElementById("autosaved");
1572
if(el != null && el != undefined && el != "") {
1574
var hh = d.getHours();
1575
var m = d.getMinutes();
1585
m = m < 10 ? "0" + m : m;
1587
el.innerHTML = "Autosaved: " + h + ":" + m + " " + dd;
1590
this.autosaveTimeoutId = setTimeout(function() {
1591
simplemde.autosave();
1592
}, this.options.autosave.delay || 10000);
1594
console.log("SimpleMDE: localStorage not available, cannot autosave");
1598
SimpleMDE.prototype.clearAutosavedValue = function() {
1599
if(isLocalStorageAvailable()) {
1600
if(this.options.autosave == undefined || this.options.autosave.uniqueId == undefined || this.options.autosave.uniqueId == "") {
1601
console.log("SimpleMDE: You must set a uniqueId to clear the autosave value");
1605
localStorage.removeItem("smde_" + this.options.autosave.uniqueId);
1607
console.log("SimpleMDE: localStorage not available, cannot autosave");
1611
SimpleMDE.prototype.createSideBySide = function() {
1612
var cm = this.codemirror;
1613
var wrapper = cm.getWrapperElement();
1614
var preview = wrapper.nextSibling;
1616
if(!preview || !/editor-preview-side/.test(preview.className)) {
1617
preview = document.createElement("div");
1618
preview.className = "editor-preview-side";
1619
wrapper.parentNode.insertBefore(preview, wrapper.nextSibling);
1622
// Syncs scroll editor -> preview
1623
var cScroll = false;
1624
var pScroll = false;
1625
cm.on("scroll", function(v) {
1631
var height = v.getScrollInfo().height - v.getScrollInfo().clientHeight;
1632
var ratio = parseFloat(v.getScrollInfo().top) / height;
1633
var move = (preview.scrollHeight - preview.clientHeight) * ratio;
1634
preview.scrollTop = move;
1637
// Syncs scroll preview -> editor
1638
preview.onscroll = function() {
1644
var height = preview.scrollHeight - preview.clientHeight;
1645
var ratio = parseFloat(preview.scrollTop) / height;
1646
var move = (cm.getScrollInfo().height - cm.getScrollInfo().clientHeight) * ratio;
1647
cm.scrollTo(0, move);
1652
SimpleMDE.prototype.createToolbar = function(items) {
1653
items = items || this.options.toolbar;
1655
if(!items || items.length === 0) {
1659
for(i = 0; i < items.length; i++) {
1660
if(toolbarBuiltInButtons[items[i]] != undefined) {
1661
items[i] = toolbarBuiltInButtons[items[i]];
1665
var bar = document.createElement("div");
1666
bar.className = "editor-toolbar";
1670
var toolbarData = {};
1671
self.toolbar = items;
1673
for(i = 0; i < items.length; i++) {
1674
if(items[i].name == "guide" && self.options.toolbarGuideIcon === false)
1677
if(self.options.hideIcons && self.options.hideIcons.indexOf(items[i].name) != -1)
1680
// Fullscreen does not work well on mobile devices (even tablets)
1681
// In the future, hopefully this can be resolved
1682
if((items[i].name == "fullscreen" || items[i].name == "side-by-side") && isMobile())
1686
// Don't include trailing separators
1687
if(items[i] === "|") {
1688
var nonSeparatorIconsFollow = false;
1690
for(var x = (i + 1); x < items.length; x++) {
1691
if(items[x] !== "|" && (!self.options.hideIcons || self.options.hideIcons.indexOf(items[x].name) == -1)) {
1692
nonSeparatorIconsFollow = true;
1696
if(!nonSeparatorIconsFollow)
1701
// Create the icon and append to the toolbar
1707
el = createIcon(item, self.options.toolbarTips, self.options.shortcuts);
1710
// bind events, special for info
1712
if(typeof item.action === "function") {
1713
el.onclick = function(e) {
1717
} else if(typeof item.action === "string") {
1718
el.href = item.action;
1719
el.target = "_blank";
1723
toolbarData[item.name || item] = el;
1724
bar.appendChild(el);
1728
self.toolbarElements = toolbarData;
1730
var cm = this.codemirror;
1731
cm.on("cursorActivity", function() {
1732
var stat = getState(cm);
1734
for(var key in toolbarData) {
1736
var el = toolbarData[key];
1738
el.className += " active";
1739
} else if(key != "fullscreen" && key != "side-by-side") {
1740
el.className = el.className.replace(/\s*active\s*/g, "");
1746
var cmWrapper = cm.getWrapperElement();
1747
cmWrapper.parentNode.insertBefore(bar, cmWrapper);
1751
SimpleMDE.prototype.createStatusbar = function(status) {
1753
status = status || this.options.status;
1754
var options = this.options;
1755
var cm = this.codemirror;
1758
// Make sure the status variable is valid
1759
if(!status || status.length === 0)
1763
// Set up the built-in items
1765
var i, onUpdate, defaultValue;
1767
for(i = 0; i < status.length; i++) {
1768
// Reset some values
1769
onUpdate = undefined;
1770
defaultValue = undefined;
1773
// Handle if custom or not
1774
if(typeof status[i] === "object") {
1776
className: status[i].className,
1777
defaultValue: status[i].defaultValue,
1778
onUpdate: status[i].onUpdate
1781
var name = status[i];
1783
if(name === "words") {
1784
defaultValue = function(el) {
1785
el.innerHTML = wordCount(cm.getValue());
1787
onUpdate = function(el) {
1788
el.innerHTML = wordCount(cm.getValue());
1790
} else if(name === "lines") {
1791
defaultValue = function(el) {
1792
el.innerHTML = cm.lineCount();
1794
onUpdate = function(el) {
1795
el.innerHTML = cm.lineCount();
1797
} else if(name === "cursor") {
1798
defaultValue = function(el) {
1799
el.innerHTML = "0:0";
1801
onUpdate = function(el) {
1802
var pos = cm.getCursor();
1803
el.innerHTML = pos.line + ":" + pos.ch;
1805
} else if(name === "autosave") {
1806
defaultValue = function(el) {
1807
if(options.autosave != undefined && options.autosave.enabled === true) {
1808
el.setAttribute("id", "autosaved");
1815
defaultValue: defaultValue,
1822
// Create element for the status bar
1823
var bar = document.createElement("div");
1824
bar.className = "editor-statusbar";
1827
// Create a new span for each item
1828
for(i = 0; i < items.length; i++) {
1829
// Store in temporary variable
1830
var item = items[i];
1833
// Create span element
1834
var el = document.createElement("span");
1835
el.className = item.className;
1838
// Ensure the defaultValue is a function
1839
if(typeof item.defaultValue === "function") {
1840
item.defaultValue(el);
1844
// Ensure the onUpdate is a function
1845
if(typeof item.onUpdate === "function") {
1846
// Create a closure around the span of the current action, then execute the onUpdate handler
1847
this.codemirror.on("update", (function(el, item) {
1855
// Append the item to the status bar
1856
bar.appendChild(el);
1860
// Insert the status bar into the DOM
1861
var cmWrapper = this.codemirror.getWrapperElement();
1862
cmWrapper.parentNode.insertBefore(bar, cmWrapper.nextSibling);
1867
* Get or set the text content.
1869
SimpleMDE.prototype.value = function(val) {
1870
if(val === undefined) {
1871
return this.codemirror.getValue();
1873
this.codemirror.getDoc().setValue(val);
1880
* Bind static methods for exports.
1882
SimpleMDE.toggleBold = toggleBold;
1883
SimpleMDE.toggleItalic = toggleItalic;
1884
SimpleMDE.toggleStrikethrough = toggleStrikethrough;
1885
SimpleMDE.toggleBlockquote = toggleBlockquote;
1886
SimpleMDE.toggleHeadingSmaller = toggleHeadingSmaller;
1887
SimpleMDE.toggleHeadingBigger = toggleHeadingBigger;
1888
SimpleMDE.toggleHeading1 = toggleHeading1;
1889
SimpleMDE.toggleHeading2 = toggleHeading2;
1890
SimpleMDE.toggleHeading3 = toggleHeading3;
1891
SimpleMDE.toggleCodeBlock = toggleCodeBlock;
1892
SimpleMDE.toggleUnorderedList = toggleUnorderedList;
1893
SimpleMDE.toggleOrderedList = toggleOrderedList;
1894
SimpleMDE.cleanBlock = cleanBlock;
1895
SimpleMDE.drawLink = drawLink;
1896
SimpleMDE.drawImage = drawImage;
1897
SimpleMDE.drawTable = drawTable;
1898
SimpleMDE.drawHorizontalRule = drawHorizontalRule;
1899
SimpleMDE.undo = undo;
1900
SimpleMDE.redo = redo;
1901
SimpleMDE.togglePreview = togglePreview;
1902
SimpleMDE.toggleSideBySide = toggleSideBySide;
1903
SimpleMDE.toggleFullScreen = toggleFullScreen;
1906
* Bind instance methods for exports.
1908
SimpleMDE.prototype.toggleBold = function() {
1911
SimpleMDE.prototype.toggleItalic = function() {
1914
SimpleMDE.prototype.toggleStrikethrough = function() {
1915
toggleStrikethrough(this);
1917
SimpleMDE.prototype.toggleBlockquote = function() {
1918
toggleBlockquote(this);
1920
SimpleMDE.prototype.toggleHeadingSmaller = function() {
1921
toggleHeadingSmaller(this);
1923
SimpleMDE.prototype.toggleHeadingBigger = function() {
1924
toggleHeadingBigger(this);
1926
SimpleMDE.prototype.toggleHeading1 = function() {
1927
toggleHeading1(this);
1929
SimpleMDE.prototype.toggleHeading2 = function() {
1930
toggleHeading2(this);
1932
SimpleMDE.prototype.toggleHeading3 = function() {
1933
toggleHeading3(this);
1935
SimpleMDE.prototype.toggleCodeBlock = function() {
1936
toggleCodeBlock(this);
1938
SimpleMDE.prototype.toggleUnorderedList = function() {
1939
toggleUnorderedList(this);
1941
SimpleMDE.prototype.toggleOrderedList = function() {
1942
toggleOrderedList(this);
1944
SimpleMDE.prototype.cleanBlock = function() {
1947
SimpleMDE.prototype.drawLink = function() {
1950
SimpleMDE.prototype.drawImage = function() {
1953
SimpleMDE.prototype.drawTable = function() {
1956
SimpleMDE.prototype.drawHorizontalRule = function() {
1957
drawHorizontalRule(this);
1959
SimpleMDE.prototype.undo = function() {
1962
SimpleMDE.prototype.redo = function() {
1965
SimpleMDE.prototype.togglePreview = function() {
1966
togglePreview(this);
1968
SimpleMDE.prototype.toggleSideBySide = function() {
1969
toggleSideBySide(this);
1971
SimpleMDE.prototype.toggleFullScreen = function() {
1972
toggleFullScreen(this);
1975
SimpleMDE.prototype.isPreviewActive = function() {
1976
var cm = this.codemirror;
1977
var wrapper = cm.getWrapperElement();
1978
var preview = wrapper.lastChild;
1980
return /editor-preview-active/.test(preview.className);
1983
SimpleMDE.prototype.isSideBySideActive = function() {
1984
var cm = this.codemirror;
1985
var wrapper = cm.getWrapperElement();
1986
var preview = wrapper.nextSibling;
1988
return /editor-preview-active-side/.test(preview.className);
1991
SimpleMDE.prototype.isFullscreenActive = function() {
1992
var cm = this.codemirror;
1994
return cm.getOption("fullScreen");
1997
SimpleMDE.prototype.getState = function() {
1998
var cm = this.codemirror;
2000
return getState(cm);
2003
SimpleMDE.prototype.toTextArea = function() {
2004
var cm = this.codemirror;
2005
var wrapper = cm.getWrapperElement();
2007
if(wrapper.parentNode) {
2008
if(this.gui.toolbar) {
2009
wrapper.parentNode.removeChild(this.gui.toolbar);
2011
if(this.gui.statusbar) {
2012
wrapper.parentNode.removeChild(this.gui.statusbar);
2014
if(this.gui.sideBySide) {
2015
wrapper.parentNode.removeChild(this.gui.sideBySide);
2021
if(this.autosaveTimeoutId) {
2022
clearTimeout(this.autosaveTimeoutId);
2023
this.autosaveTimeoutId = undefined;
2024
this.clearAutosavedValue();
2028
module.exports = SimpleMDE;