~oly/scaffold/trunk

« back to all changes in this revision

Viewing changes to scaffold/www/default_widgets/js/simplemde.js

  • Committer: Oliver Marks
  • Date: 2016-09-18 16:32:41 UTC
  • Revision ID: oly@digitaloctave.com-20160918163241-1y6lhshwr3ucn2tz
python3 changes, fixes to tests

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*global require,module*/
 
2
"use strict";
 
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");
 
15
 
 
16
 
 
17
// Some variables
 
18
var isMac = /Mac/.test(navigator.platform);
 
19
 
 
20
// Mapping of actions that can be bound to keyboard shortcuts or toolbar buttons
 
21
var bindings = {
 
22
        "toggleBold": toggleBold,
 
23
        "toggleItalic": toggleItalic,
 
24
        "drawLink": drawLink,
 
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,
 
40
        "undo": undo,
 
41
        "redo": redo,
 
42
        "toggleSideBySide": toggleSideBySide,
 
43
        "toggleFullScreen": toggleFullScreen
 
44
};
 
45
 
 
46
var shortcuts = {
 
47
        "toggleBold": "Cmd-B",
 
48
        "toggleItalic": "Cmd-I",
 
49
        "drawLink": "Cmd-K",
 
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"
 
61
};
 
62
 
 
63
var getBindingName = function(f) {
 
64
        for(var key in bindings) {
 
65
                if(bindings[key] === f) {
 
66
                        return key;
 
67
                }
 
68
        }
 
69
        return null;
 
70
};
 
71
 
 
72
var isMobile = function() {
 
73
        var check = false;
 
74
        (function(a) {
 
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);
 
77
        return check;
 
78
};
 
79
 
 
80
 
 
81
/**
 
82
 * Fix shortcut. Mac use Command, others use Ctrl.
 
83
 */
 
84
function fixShortcut(name) {
 
85
        if(isMac) {
 
86
                name = name.replace("Ctrl", "Cmd");
 
87
        } else {
 
88
                name = name.replace("Cmd", "Ctrl");
 
89
        }
 
90
        return name;
 
91
}
 
92
 
 
93
 
 
94
/**
 
95
 * Create icon element for toolbar.
 
96
 */
 
97
function createIcon(options, enableTooltips, shortcuts) {
 
98
        options = options || {};
 
99
        var el = document.createElement("a");
 
100
        enableTooltips = (enableTooltips == undefined) ? true : enableTooltips;
 
101
 
 
102
        if(options.title && enableTooltips) {
 
103
                el.title = createTootlip(options.title, options.action, shortcuts);
 
104
 
 
105
                if(isMac) {
 
106
                        el.title = el.title.replace("Ctrl", "⌘");
 
107
                        el.title = el.title.replace("Alt", "⌥");
 
108
                }
 
109
        }
 
110
 
 
111
        el.tabIndex = -1;
 
112
        el.className = options.className;
 
113
        return el;
 
114
}
 
115
 
 
116
function createSep() {
 
117
        var el = document.createElement("i");
 
118
        el.className = "separator";
 
119
        el.innerHTML = "|";
 
120
        return el;
 
121
}
 
122
 
 
123
function createTootlip(title, action, shortcuts) {
 
124
        var actionName;
 
125
        var tooltip = title;
 
126
 
 
127
        if(action) {
 
128
                actionName = getBindingName(action);
 
129
                if(shortcuts[actionName]) {
 
130
                        tooltip += " (" + fixShortcut(shortcuts[actionName]) + ")";
 
131
                }
 
132
        }
 
133
 
 
134
        return tooltip;
 
135
}
 
136
 
 
137
/**
 
138
 * The state of CodeMirror at the given position.
 
139
 */
 
140
function getState(cm, pos) {
 
141
        pos = pos || cm.getCursor("start");
 
142
        var stat = cm.getTokenAt(pos);
 
143
        if(!stat.type) return {};
 
144
 
 
145
        var types = stat.type.split(" ");
 
146
 
 
147
        var ret = {},
 
148
                data, text;
 
149
        for(var i = 0; i < types.length; i++) {
 
150
                data = types[i];
 
151
                if(data === "strong") {
 
152
                        ret.bold = true;
 
153
                } else if(data === "variable-2") {
 
154
                        text = cm.getLine(pos.line);
 
155
                        if(/^\s*\d+\.\s/.test(text)) {
 
156
                                ret["ordered-list"] = true;
 
157
                        } else {
 
158
                                ret["unordered-list"] = true;
 
159
                        }
 
160
                } else if(data === "atom") {
 
161
                        ret.quote = true;
 
162
                } else if(data === "em") {
 
163
                        ret.italic = true;
 
164
                } else if(data === "quote") {
 
165
                        ret.quote = true;
 
166
                } else if(data === "strikethrough") {
 
167
                        ret.strikethrough = true;
 
168
                } else if(data === "comment") {
 
169
                        ret.code = true;
 
170
                } else if(data === "link") {
 
171
                        ret.link = true;
 
172
                } else if(data === "tag") {
 
173
                        ret.image = true;
 
174
                } else if(data.match(/^header(\-[1-6])?$/)) {
 
175
                        ret[data.replace("header", "heading")] = true;
 
176
                }
 
177
        }
 
178
        return ret;
 
179
}
 
180
 
 
181
 
 
182
// Saved overflow setting
 
183
var saved_overflow = "";
 
184
 
 
185
/**
 
186
 * Toggle full screen of the editor.
 
187
 */
 
188
function toggleFullScreen(editor) {
 
189
        // Set fullscreen
 
190
        var cm = editor.codemirror;
 
191
        cm.setOption("fullScreen", !cm.getOption("fullScreen"));
 
192
 
 
193
 
 
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";
 
198
        } else {
 
199
                document.body.style.overflow = saved_overflow;
 
200
        }
 
201
 
 
202
 
 
203
        // Update toolbar class
 
204
        var wrap = cm.getWrapperElement();
 
205
 
 
206
        if(!/fullscreen/.test(wrap.previousSibling.className)) {
 
207
                wrap.previousSibling.className += " fullscreen";
 
208
        } else {
 
209
                wrap.previousSibling.className = wrap.previousSibling.className.replace(/\s*fullscreen\b/, "");
 
210
        }
 
211
 
 
212
 
 
213
        // Update toolbar button
 
214
        var toolbarButton = editor.toolbarElements.fullscreen;
 
215
 
 
216
        if(!/active/.test(toolbarButton.className)) {
 
217
                toolbarButton.className += " active";
 
218
        } else {
 
219
                toolbarButton.className = toolbarButton.className.replace(/\s*active\s*/g, "");
 
220
        }
 
221
 
 
222
 
 
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);
 
227
}
 
228
 
 
229
 
 
230
/**
 
231
 * Action for toggling bold.
 
232
 */
 
233
function toggleBold(editor) {
 
234
        _toggleBlock(editor, "bold", editor.options.blockStyles.bold);
 
235
}
 
236
 
 
237
 
 
238
/**
 
239
 * Action for toggling italic.
 
240
 */
 
241
function toggleItalic(editor) {
 
242
        _toggleBlock(editor, "italic", editor.options.blockStyles.italic);
 
243
}
 
244
 
 
245
 
 
246
/**
 
247
 * Action for toggling strikethrough.
 
248
 */
 
249
function toggleStrikethrough(editor) {
 
250
        _toggleBlock(editor, "strikethrough", "~~");
 
251
}
 
252
 
 
253
/**
 
254
 * Action for toggling code block.
 
255
 */
 
256
function toggleCodeBlock(editor) {
 
257
        var fenceCharsToInsert = editor.options.blockStyles.code;
 
258
 
 
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;
 
263
                }
 
264
                return line.styles && line.styles[2] && line.styles[2].indexOf("formatting-code-block") !== -1;
 
265
        }
 
266
 
 
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;
 
270
        }
 
271
 
 
272
        function code_type(cm, line_num, line, firstTok, lastTok) {
 
273
                /*
 
274
                 * Return "single", "indented", "fenced" or false
 
275
                 *
 
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.
 
278
                 */
 
279
                line = line || cm.getLineHandle(line_num);
 
280
                firstTok = firstTok || cm.getTokenAt({
 
281
                        line: line_num,
 
282
                        ch: 1
 
283
                });
 
284
                lastTok = lastTok || (!!line.text && cm.getTokenAt({
 
285
                        line: line_num,
 
286
                        ch: line.text.length - 1
 
287
                }));
 
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
 
291
                        return "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
 
294
                        return false;
 
295
                } else if(token_state(firstTok).fencedChars || token_state(lastTok).fencedChars || fencing_line(line)) {
 
296
                        return "fenced";
 
297
                } else {
 
298
                        return "single";
 
299
                }
 
300
        }
 
301
 
 
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;
 
308
                if(sel_multi) {
 
309
                        end_line_sel++;
 
310
                }
 
311
                // handle last char including \n or not
 
312
                if(sel_multi && cur_end.ch === 0) {
 
313
                        repl_end = fenceCharsToInsert + "\n";
 
314
                        end_line_sel--;
 
315
                }
 
316
                _replaceSelection(cm, false, [repl_start, repl_end]);
 
317
                cm.setSelection({
 
318
                        line: start_line_sel,
 
319
                        ch: 0
 
320
                }, {
 
321
                        line: end_line_sel,
 
322
                        ch: 0
 
323
                });
 
324
        }
 
325
 
 
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;
 
336
 
 
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,
 
343
                        ch: 0
 
344
                }, {
 
345
                        line: cur_start.line,
 
346
                        ch: 99999999999999
 
347
                });
 
348
                cur_start.ch--;
 
349
                if(cur_start !== cur_end) {
 
350
                        cur_end.ch--;
 
351
                }
 
352
                cm.setSelection(cur_start, cur_end);
 
353
                cm.focus();
 
354
        } else if(is_code === "fenced") {
 
355
                if(cur_start.line !== cur_end.line || cur_start.ch !== cur_end.ch) {
 
356
                        // use selection
 
357
 
 
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)) {
 
362
                                        break;
 
363
                                }
 
364
                        }
 
365
                        var fencedTok = cm.getTokenAt({
 
366
                                line: block_start,
 
367
                                ch: 1
 
368
                        });
 
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))) {
 
374
                                start_text = "";
 
375
                                start_line = cur_start.line;
 
376
                        } else if(fencing_line(cm.getLineHandle(cur_start.line - 1))) {
 
377
                                start_text = "";
 
378
                                start_line = cur_start.line - 1;
 
379
                        } else {
 
380
                                start_text = fence_chars + "\n";
 
381
                                start_line = cur_start.line;
 
382
                        }
 
383
                        if(fencing_line(cm.getLineHandle(cur_end.line))) {
 
384
                                end_text = "";
 
385
                                end_line = cur_end.line;
 
386
                                if(cur_end.ch === 0) {
 
387
                                        end_line += 1;
 
388
                                }
 
389
                        } else if(cur_end.ch !== 0 && fencing_line(cm.getLineHandle(cur_end.line + 1))) {
 
390
                                end_text = "";
 
391
                                end_line = cur_end.line + 1;
 
392
                        } else {
 
393
                                end_text = fence_chars + "\n";
 
394
                                end_line = cur_end.line + 1;
 
395
                        }
 
396
                        if(cur_end.ch === 0) {
 
397
                                // full last line selected, putting cursor at beginning of next
 
398
                                end_line -= 1;
 
399
                        }
 
400
                        cm.operation(function() {
 
401
                                // end line first, so that line numbers don't change
 
402
                                cm.replaceRange(end_text, {
 
403
                                        line: end_line,
 
404
                                        ch: 0
 
405
                                }, {
 
406
                                        line: end_line + (end_text ? 0 : 1),
 
407
                                        ch: 0
 
408
                                });
 
409
                                cm.replaceRange(start_text, {
 
410
                                        line: start_line,
 
411
                                        ch: 0
 
412
                                }, {
 
413
                                        line: start_line + (start_text ? 0 : 1),
 
414
                                        ch: 0
 
415
                                });
 
416
                        });
 
417
                        cm.setSelection({
 
418
                                line: start_line + (start_text ? 1 : 0),
 
419
                                ch: 0
 
420
                        }, {
 
421
                                line: end_line + (start_text ? 1 : -1),
 
422
                                ch: 0
 
423
                        });
 
424
                        cm.focus();
 
425
                } else {
 
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"
 
432
                                } else {
 
433
                                        block_end = cur_start.line;
 
434
                                        search_from = cur_start.line - 1; // for searching for "start"
 
435
                                }
 
436
                        }
 
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)) {
 
441
                                                break;
 
442
                                        }
 
443
                                }
 
444
                        }
 
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)) {
 
450
                                                break;
 
451
                                        }
 
452
                                }
 
453
                        }
 
454
                        cm.operation(function() {
 
455
                                cm.replaceRange("", {
 
456
                                        line: block_start,
 
457
                                        ch: 0
 
458
                                }, {
 
459
                                        line: block_start + 1,
 
460
                                        ch: 0
 
461
                                });
 
462
                                cm.replaceRange("", {
 
463
                                        line: block_end - 1,
 
464
                                        ch: 0
 
465
                                }, {
 
466
                                        line: block_end,
 
467
                                        ch: 0
 
468
                                });
 
469
                        });
 
470
                        cm.focus();
 
471
                }
 
472
        } else if(is_code === "indented") {
 
473
                if(cur_start.line !== cur_end.line || cur_start.ch !== cur_end.ch) {
 
474
                        // use selection
 
475
                        block_start = cur_start.line;
 
476
                        block_end = cur_end.line;
 
477
                        if(cur_end.ch === 0) {
 
478
                                block_end--;
 
479
                        }
 
480
                } else {
 
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
 
486
                                        continue;
 
487
                                } else {
 
488
                                        if(code_type(cm, block_start, line) !== "indented") {
 
489
                                                block_start += 1;
 
490
                                                break;
 
491
                                        }
 
492
                                }
 
493
                        }
 
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
 
499
                                        continue;
 
500
                                } else {
 
501
                                        if(code_type(cm, block_end, line) !== "indented") {
 
502
                                                block_end -= 1;
 
503
                                                break;
 
504
                                        }
 
505
                                }
 
506
                        }
 
507
                }
 
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({
 
512
                                line: block_end + 1,
 
513
                                ch: next_line.text.length - 1
 
514
                        }),
 
515
                        next_line_indented = next_line_last_tok && token_state(next_line_last_tok).indentedCode;
 
516
                if(next_line_indented) {
 
517
                        cm.replaceRange("\n", {
 
518
                                line: block_end + 1,
 
519
                                ch: 0
 
520
                        });
 
521
                }
 
522
 
 
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 :(
 
525
                }
 
526
                cm.focus();
 
527
        } else {
 
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);
 
533
                } else {
 
534
                        _replaceSelection(cm, false, ["`", "`"]);
 
535
                }
 
536
        }
 
537
}
 
538
 
 
539
/**
 
540
 * Action for toggling blockquote.
 
541
 */
 
542
function toggleBlockquote(editor) {
 
543
        var cm = editor.codemirror;
 
544
        _toggleLine(cm, "quote");
 
545
}
 
546
 
 
547
/**
 
548
 * Action for toggling heading size: normal -> h1 -> h2 -> h3 -> h4 -> h5 -> h6 -> normal
 
549
 */
 
550
function toggleHeadingSmaller(editor) {
 
551
        var cm = editor.codemirror;
 
552
        _toggleHeading(cm, "smaller");
 
553
}
 
554
 
 
555
/**
 
556
 * Action for toggling heading size: normal -> h6 -> h5 -> h4 -> h3 -> h2 -> h1 -> normal
 
557
 */
 
558
function toggleHeadingBigger(editor) {
 
559
        var cm = editor.codemirror;
 
560
        _toggleHeading(cm, "bigger");
 
561
}
 
562
 
 
563
/**
 
564
 * Action for toggling heading size 1
 
565
 */
 
566
function toggleHeading1(editor) {
 
567
        var cm = editor.codemirror;
 
568
        _toggleHeading(cm, undefined, 1);
 
569
}
 
570
 
 
571
/**
 
572
 * Action for toggling heading size 2
 
573
 */
 
574
function toggleHeading2(editor) {
 
575
        var cm = editor.codemirror;
 
576
        _toggleHeading(cm, undefined, 2);
 
577
}
 
578
 
 
579
/**
 
580
 * Action for toggling heading size 3
 
581
 */
 
582
function toggleHeading3(editor) {
 
583
        var cm = editor.codemirror;
 
584
        _toggleHeading(cm, undefined, 3);
 
585
}
 
586
 
 
587
 
 
588
/**
 
589
 * Action for toggling ul.
 
590
 */
 
591
function toggleUnorderedList(editor) {
 
592
        var cm = editor.codemirror;
 
593
        _toggleLine(cm, "unordered-list");
 
594
}
 
595
 
 
596
 
 
597
/**
 
598
 * Action for toggling ol.
 
599
 */
 
600
function toggleOrderedList(editor) {
 
601
        var cm = editor.codemirror;
 
602
        _toggleLine(cm, "ordered-list");
 
603
}
 
604
 
 
605
/**
 
606
 * Action for clean block (remove headline, list, blockquote code, markers)
 
607
 */
 
608
function cleanBlock(editor) {
 
609
        var cm = editor.codemirror;
 
610
        _cleanBlock(cm);
 
611
}
 
612
 
 
613
/**
 
614
 * Action for drawing a link.
 
615
 */
 
616
function drawLink(editor) {
 
617
        var cm = editor.codemirror;
 
618
        var stat = getState(cm);
 
619
        var options = editor.options;
 
620
        var url = "http://";
 
621
        if(options.promptURLs) {
 
622
                url = prompt(options.promptTexts.link);
 
623
                if(!url) {
 
624
                        return false;
 
625
                }
 
626
        }
 
627
        _replaceSelection(cm, stat.link, options.insertTexts.link, url);
 
628
}
 
629
 
 
630
/**
 
631
 * Action for drawing an img.
 
632
 */
 
633
function drawImage(editor) {
 
634
        var cm = editor.codemirror;
 
635
        var stat = getState(cm);
 
636
        var options = editor.options;
 
637
        var url = "http://";
 
638
        if(options.promptURLs) {
 
639
                url = prompt(options.promptTexts.image);
 
640
                if(!url) {
 
641
                        return false;
 
642
                }
 
643
        }
 
644
        _replaceSelection(cm, stat.image, options.insertTexts.image, url);
 
645
}
 
646
 
 
647
/**
 
648
 * Action for drawing a table.
 
649
 */
 
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);
 
655
}
 
656
 
 
657
/**
 
658
 * Action for drawing a horizontal rule.
 
659
 */
 
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);
 
665
}
 
666
 
 
667
 
 
668
/**
 
669
 * Undo action.
 
670
 */
 
671
function undo(editor) {
 
672
        var cm = editor.codemirror;
 
673
        cm.undo();
 
674
        cm.focus();
 
675
}
 
676
 
 
677
 
 
678
/**
 
679
 * Redo action.
 
680
 */
 
681
function redo(editor) {
 
682
        var cm = editor.codemirror;
 
683
        cm.redo();
 
684
        cm.focus();
 
685
}
 
686
 
 
687
 
 
688
/**
 
689
 * Toggle side by side preview
 
690
 */
 
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, ""
 
700
                );
 
701
                toolbarButton.className = toolbarButton.className.replace(/\s*active\s*/g, "");
 
702
                wrapper.className = wrapper.className.replace(/\s*CodeMirror-sided\s*/g, " ");
 
703
        } else {
 
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";
 
711
                }, 1);
 
712
                toolbarButton.className += " active";
 
713
                wrapper.className += " CodeMirror-sided";
 
714
                useSideBySideListener = true;
 
715
        }
 
716
 
 
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, ""
 
722
                );
 
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, "");
 
727
        }
 
728
 
 
729
        var sideBySideRenderingFunction = function() {
 
730
                preview.innerHTML = editor.options.previewRender(editor.value(), preview);
 
731
        };
 
732
 
 
733
        if(!cm.sideBySideRenderingFunction) {
 
734
                cm.sideBySideRenderingFunction = sideBySideRenderingFunction;
 
735
        }
 
736
 
 
737
        if(useSideBySideListener) {
 
738
                preview.innerHTML = editor.options.previewRender(editor.value(), preview);
 
739
                cm.on("update", cm.sideBySideRenderingFunction);
 
740
        } else {
 
741
                cm.off("update", cm.sideBySideRenderingFunction);
 
742
        }
 
743
 
 
744
        // Refresh to fix selection being off (#309)
 
745
        cm.refresh();
 
746
}
 
747
 
 
748
 
 
749
/**
 
750
 * Preview action.
 
751
 */
 
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);
 
762
        }
 
763
        if(/editor-preview-active/.test(preview.className)) {
 
764
                preview.className = preview.className.replace(
 
765
                        /\s*editor-preview-active\s*/g, ""
 
766
                );
 
767
                if(toolbar) {
 
768
                        toolbar.className = toolbar.className.replace(/\s*active\s*/g, "");
 
769
                        toolbar_div.className = toolbar_div.className.replace(/\s*disabled-for-preview*/g, "");
 
770
                }
 
771
        } else {
 
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";
 
777
                }, 1);
 
778
                if(toolbar) {
 
779
                        toolbar.className += " active";
 
780
                        toolbar_div.className += " disabled-for-preview";
 
781
                }
 
782
        }
 
783
        preview.innerHTML = editor.options.previewRender(editor.value(), preview);
 
784
 
 
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);
 
789
}
 
790
 
 
791
function _replaceSelection(cm, active, startEnd, url) {
 
792
        if(/editor-preview-active/.test(cm.getWrapperElement().lastChild.className))
 
793
                return;
 
794
 
 
795
        var text;
 
796
        var start = startEnd[0];
 
797
        var end = startEnd[1];
 
798
        var startPoint = cm.getCursor("start");
 
799
        var endPoint = cm.getCursor("end");
 
800
        if(url) {
 
801
                end = end.replace("#url#", url);
 
802
        }
 
803
        if(active) {
 
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,
 
809
                        ch: 0
 
810
                });
 
811
        } else {
 
812
                text = cm.getSelection();
 
813
                cm.replaceSelection(start + text + end);
 
814
 
 
815
                startPoint.ch += start.length;
 
816
                if(startPoint !== endPoint) {
 
817
                        endPoint.ch += start.length;
 
818
                }
 
819
        }
 
820
        cm.setSelection(startPoint, endPoint);
 
821
        cm.focus();
 
822
}
 
823
 
 
824
 
 
825
function _toggleHeading(cm, direction, size) {
 
826
        if(/editor-preview-active/.test(cm.getWrapperElement().lastChild.className))
 
827
                return;
 
828
 
 
829
        var startPoint = cm.getCursor("start");
 
830
        var endPoint = cm.getCursor("end");
 
831
        for(var i = startPoint.line; i <= endPoint.line; i++) {
 
832
                (function(i) {
 
833
                        var text = cm.getLine(i);
 
834
                        var currHeadingLevel = text.search(/[^#]/);
 
835
 
 
836
                        if(direction !== undefined) {
 
837
                                if(currHeadingLevel <= 0) {
 
838
                                        if(direction == "bigger") {
 
839
                                                text = "###### " + text;
 
840
                                        } else {
 
841
                                                text = "# " + text;
 
842
                                        }
 
843
                                } else if(currHeadingLevel == 6 && direction == "smaller") {
 
844
                                        text = text.substr(7);
 
845
                                } else if(currHeadingLevel == 1 && direction == "bigger") {
 
846
                                        text = text.substr(2);
 
847
                                } else {
 
848
                                        if(direction == "bigger") {
 
849
                                                text = text.substr(1);
 
850
                                        } else {
 
851
                                                text = "#" + text;
 
852
                                        }
 
853
                                }
 
854
                        } else {
 
855
                                if(size == 1) {
 
856
                                        if(currHeadingLevel <= 0) {
 
857
                                                text = "# " + text;
 
858
                                        } else if(currHeadingLevel == size) {
 
859
                                                text = text.substr(currHeadingLevel + 1);
 
860
                                        } else {
 
861
                                                text = "# " + text.substr(currHeadingLevel + 1);
 
862
                                        }
 
863
                                } else if(size == 2) {
 
864
                                        if(currHeadingLevel <= 0) {
 
865
                                                text = "## " + text;
 
866
                                        } else if(currHeadingLevel == size) {
 
867
                                                text = text.substr(currHeadingLevel + 1);
 
868
                                        } else {
 
869
                                                text = "## " + text.substr(currHeadingLevel + 1);
 
870
                                        }
 
871
                                } else {
 
872
                                        if(currHeadingLevel <= 0) {
 
873
                                                text = "### " + text;
 
874
                                        } else if(currHeadingLevel == size) {
 
875
                                                text = text.substr(currHeadingLevel + 1);
 
876
                                        } else {
 
877
                                                text = "### " + text.substr(currHeadingLevel + 1);
 
878
                                        }
 
879
                                }
 
880
                        }
 
881
 
 
882
                        cm.replaceRange(text, {
 
883
                                line: i,
 
884
                                ch: 0
 
885
                        }, {
 
886
                                line: i,
 
887
                                ch: 99999999999999
 
888
                        });
 
889
                })(i);
 
890
        }
 
891
        cm.focus();
 
892
}
 
893
 
 
894
 
 
895
function _toggleLine(cm, name) {
 
896
        if(/editor-preview-active/.test(cm.getWrapperElement().lastChild.className))
 
897
                return;
 
898
 
 
899
        var stat = getState(cm);
 
900
        var startPoint = cm.getCursor("start");
 
901
        var endPoint = cm.getCursor("end");
 
902
        var repl = {
 
903
                "quote": /^(\s*)\>\s+/,
 
904
                "unordered-list": /^(\s*)(\*|\-|\+)\s+/,
 
905
                "ordered-list": /^(\s*)\d+\.\s+/
 
906
        };
 
907
        var map = {
 
908
                "quote": "> ",
 
909
                "unordered-list": "* ",
 
910
                "ordered-list": "1. "
 
911
        };
 
912
        for(var i = startPoint.line; i <= endPoint.line; i++) {
 
913
                (function(i) {
 
914
                        var text = cm.getLine(i);
 
915
                        if(stat[name]) {
 
916
                                text = text.replace(repl[name], "$1");
 
917
                        } else {
 
918
                                text = map[name] + text;
 
919
                        }
 
920
                        cm.replaceRange(text, {
 
921
                                line: i,
 
922
                                ch: 0
 
923
                        }, {
 
924
                                line: i,
 
925
                                ch: 99999999999999
 
926
                        });
 
927
                })(i);
 
928
        }
 
929
        cm.focus();
 
930
}
 
931
 
 
932
function _toggleBlock(editor, type, start_chars, end_chars) {
 
933
        if(/editor-preview-active/.test(editor.codemirror.getWrapperElement().lastChild.className))
 
934
                return;
 
935
 
 
936
        end_chars = (typeof end_chars === "undefined") ? start_chars : end_chars;
 
937
        var cm = editor.codemirror;
 
938
        var stat = getState(cm);
 
939
 
 
940
        var text;
 
941
        var start = start_chars;
 
942
        var end = end_chars;
 
943
 
 
944
        var startPoint = cm.getCursor("start");
 
945
        var endPoint = cm.getCursor("end");
 
946
 
 
947
        if(stat[type]) {
 
948
                text = cm.getLine(startPoint.line);
 
949
                start = text.slice(0, startPoint.ch);
 
950
                end = text.slice(startPoint.ch);
 
951
                if(type == "bold") {
 
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(/(\*\*|~~)/, "");
 
960
                }
 
961
                cm.replaceRange(start + end, {
 
962
                        line: startPoint.line,
 
963
                        ch: 0
 
964
                }, {
 
965
                        line: startPoint.line,
 
966
                        ch: 99999999999999
 
967
                });
 
968
 
 
969
                if(type == "bold" || type == "strikethrough") {
 
970
                        startPoint.ch -= 2;
 
971
                        if(startPoint !== endPoint) {
 
972
                                endPoint.ch -= 2;
 
973
                        }
 
974
                } else if(type == "italic") {
 
975
                        startPoint.ch -= 1;
 
976
                        if(startPoint !== endPoint) {
 
977
                                endPoint.ch -= 1;
 
978
                        }
 
979
                }
 
980
        } else {
 
981
                text = cm.getSelection();
 
982
                if(type == "bold") {
 
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("");
 
990
                }
 
991
                cm.replaceSelection(start + text + end);
 
992
 
 
993
                startPoint.ch += start_chars.length;
 
994
                endPoint.ch = startPoint.ch + text.length;
 
995
        }
 
996
 
 
997
        cm.setSelection(startPoint, endPoint);
 
998
        cm.focus();
 
999
}
 
1000
 
 
1001
function _cleanBlock(cm) {
 
1002
        if(/editor-preview-active/.test(cm.getWrapperElement().lastChild.className))
 
1003
                return;
 
1004
 
 
1005
        var startPoint = cm.getCursor("start");
 
1006
        var endPoint = cm.getCursor("end");
 
1007
        var text;
 
1008
 
 
1009
        for(var line = startPoint.line; line <= endPoint.line; line++) {
 
1010
                text = cm.getLine(line);
 
1011
                text = text.replace(/^[ ]*([# ]+|\*|\-|[> ]+|[0-9]+(.|\)))[ ]*/, "");
 
1012
 
 
1013
                cm.replaceRange(text, {
 
1014
                        line: line,
 
1015
                        ch: 0
 
1016
                }, {
 
1017
                        line: line,
 
1018
                        ch: 99999999999999
 
1019
                });
 
1020
        }
 
1021
}
 
1022
 
 
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] : []);
 
1029
                        } else if(
 
1030
                                source[property] !== null &&
 
1031
                                typeof source[property] === "object" &&
 
1032
                                source[property].constructor === Object
 
1033
                        ) {
 
1034
                                target[property] = _mergeProperties(target[property] || {}, source[property]);
 
1035
                        } else {
 
1036
                                target[property] = source[property];
 
1037
                        }
 
1038
                }
 
1039
        }
 
1040
 
 
1041
        return target;
 
1042
}
 
1043
 
 
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]);
 
1048
        }
 
1049
 
 
1050
        return target;
 
1051
}
 
1052
 
 
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);
 
1057
        var count = 0;
 
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;
 
1062
                } else {
 
1063
                        count += 1;
 
1064
                }
 
1065
        }
 
1066
        return count;
 
1067
}
 
1068
 
 
1069
var toolbarBuiltInButtons = {
 
1070
        "bold": {
 
1071
                name: "bold",
 
1072
                action: toggleBold,
 
1073
                className: "fa fa-bold",
 
1074
                title: "Bold",
 
1075
                default: true
 
1076
        },
 
1077
        "italic": {
 
1078
                name: "italic",
 
1079
                action: toggleItalic,
 
1080
                className: "fa fa-italic",
 
1081
                title: "Italic",
 
1082
                default: true
 
1083
        },
 
1084
        "strikethrough": {
 
1085
                name: "strikethrough",
 
1086
                action: toggleStrikethrough,
 
1087
                className: "fa fa-strikethrough",
 
1088
                title: "Strikethrough"
 
1089
        },
 
1090
        "heading": {
 
1091
                name: "heading",
 
1092
                action: toggleHeadingSmaller,
 
1093
                className: "fa fa-header",
 
1094
                title: "Heading",
 
1095
                default: true
 
1096
        },
 
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"
 
1102
        },
 
1103
        "heading-bigger": {
 
1104
                name: "heading-bigger",
 
1105
                action: toggleHeadingBigger,
 
1106
                className: "fa fa-header fa-header-x fa-header-bigger",
 
1107
                title: "Bigger Heading"
 
1108
        },
 
1109
        "heading-1": {
 
1110
                name: "heading-1",
 
1111
                action: toggleHeading1,
 
1112
                className: "fa fa-header fa-header-x fa-header-1",
 
1113
                title: "Big Heading"
 
1114
        },
 
1115
        "heading-2": {
 
1116
                name: "heading-2",
 
1117
                action: toggleHeading2,
 
1118
                className: "fa fa-header fa-header-x fa-header-2",
 
1119
                title: "Medium Heading"
 
1120
        },
 
1121
        "heading-3": {
 
1122
                name: "heading-3",
 
1123
                action: toggleHeading3,
 
1124
                className: "fa fa-header fa-header-x fa-header-3",
 
1125
                title: "Small Heading"
 
1126
        },
 
1127
        "separator-1": {
 
1128
                name: "separator-1"
 
1129
        },
 
1130
        "code": {
 
1131
                name: "code",
 
1132
                action: toggleCodeBlock,
 
1133
                className: "fa fa-code",
 
1134
                title: "Code"
 
1135
        },
 
1136
        "quote": {
 
1137
                name: "quote",
 
1138
                action: toggleBlockquote,
 
1139
                className: "fa fa-quote-left",
 
1140
                title: "Quote",
 
1141
                default: true
 
1142
        },
 
1143
        "unordered-list": {
 
1144
                name: "unordered-list",
 
1145
                action: toggleUnorderedList,
 
1146
                className: "fa fa-list-ul",
 
1147
                title: "Generic List",
 
1148
                default: true
 
1149
        },
 
1150
        "ordered-list": {
 
1151
                name: "ordered-list",
 
1152
                action: toggleOrderedList,
 
1153
                className: "fa fa-list-ol",
 
1154
                title: "Numbered List",
 
1155
                default: true
 
1156
        },
 
1157
        "clean-block": {
 
1158
                name: "clean-block",
 
1159
                action: cleanBlock,
 
1160
                className: "fa fa-eraser fa-clean-block",
 
1161
                title: "Clean block"
 
1162
        },
 
1163
        "separator-2": {
 
1164
                name: "separator-2"
 
1165
        },
 
1166
        "link": {
 
1167
                name: "link",
 
1168
                action: drawLink,
 
1169
                className: "fa fa-link",
 
1170
                title: "Create Link",
 
1171
                default: true
 
1172
        },
 
1173
        "image": {
 
1174
                name: "image",
 
1175
                action: drawImage,
 
1176
                className: "fa fa-picture-o",
 
1177
                title: "Insert Image",
 
1178
                default: true
 
1179
        },
 
1180
        "table": {
 
1181
                name: "table",
 
1182
                action: drawTable,
 
1183
                className: "fa fa-table",
 
1184
                title: "Insert Table"
 
1185
        },
 
1186
        "horizontal-rule": {
 
1187
                name: "horizontal-rule",
 
1188
                action: drawHorizontalRule,
 
1189
                className: "fa fa-minus",
 
1190
                title: "Insert Horizontal Line"
 
1191
        },
 
1192
        "separator-3": {
 
1193
                name: "separator-3"
 
1194
        },
 
1195
        "preview": {
 
1196
                name: "preview",
 
1197
                action: togglePreview,
 
1198
                className: "fa fa-eye no-disable",
 
1199
                title: "Toggle Preview",
 
1200
                default: true
 
1201
        },
 
1202
        "side-by-side": {
 
1203
                name: "side-by-side",
 
1204
                action: toggleSideBySide,
 
1205
                className: "fa fa-columns no-disable no-mobile",
 
1206
                title: "Toggle Side by Side",
 
1207
                default: true
 
1208
        },
 
1209
        "fullscreen": {
 
1210
                name: "fullscreen",
 
1211
                action: toggleFullScreen,
 
1212
                className: "fa fa-arrows-alt no-disable no-mobile",
 
1213
                title: "Toggle Fullscreen",
 
1214
                default: true
 
1215
        },
 
1216
        "separator-4": {
 
1217
                name: "separator-4"
 
1218
        },
 
1219
        "guide": {
 
1220
                name: "guide",
 
1221
                action: "https://simplemde.com/markdown-guide",
 
1222
                className: "fa fa-question-circle",
 
1223
                title: "Markdown Guide",
 
1224
                default: true
 
1225
        },
 
1226
        "separator-5": {
 
1227
                name: "separator-5"
 
1228
        },
 
1229
        "undo": {
 
1230
                name: "undo",
 
1231
                action: undo,
 
1232
                className: "fa fa-undo no-disable",
 
1233
                title: "Undo"
 
1234
        },
 
1235
        "redo": {
 
1236
                name: "redo",
 
1237
                action: redo,
 
1238
                className: "fa fa-repeat no-disable",
 
1239
                title: "Redo"
 
1240
        }
 
1241
};
 
1242
 
 
1243
var insertTexts = {
 
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"]
 
1248
};
 
1249
 
 
1250
var promptTexts = {
 
1251
        link: "URL for the link:",
 
1252
        image: "URL of the image:"
 
1253
};
 
1254
 
 
1255
var blockStyles = {
 
1256
        "bold": "**",
 
1257
        "code": "```",
 
1258
        "italic": "*"
 
1259
};
 
1260
 
 
1261
/**
 
1262
 * Interface of SimpleMDE.
 
1263
 */
 
1264
function SimpleMDE(options) {
 
1265
        // Handle options parameter
 
1266
        options = options || {};
 
1267
 
 
1268
 
 
1269
        // Used later to refer to it"s parent
 
1270
        options.parent = this;
 
1271
 
 
1272
 
 
1273
        // Check if Font Awesome needs to be auto downloaded
 
1274
        var autoDownloadFA = true;
 
1275
 
 
1276
        if(options.autoDownloadFontAwesome === false) {
 
1277
                autoDownloadFA = false;
 
1278
        }
 
1279
 
 
1280
        if(options.autoDownloadFontAwesome !== true) {
 
1281
                var styleSheets = document.styleSheets;
 
1282
                for(var i = 0; i < styleSheets.length; i++) {
 
1283
                        if(!styleSheets[i].href)
 
1284
                                continue;
 
1285
 
 
1286
                        if(styleSheets[i].href.indexOf("//maxcdn.bootstrapcdn.com/font-awesome/") > -1) {
 
1287
                                autoDownloadFA = false;
 
1288
                        }
 
1289
                }
 
1290
        }
 
1291
 
 
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);
 
1297
        }
 
1298
 
 
1299
 
 
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.");
 
1306
                return;
 
1307
        }
 
1308
 
 
1309
 
 
1310
        // Handle toolbar
 
1311
        if(options.toolbar === undefined) {
 
1312
                // Initialize
 
1313
                options.toolbar = [];
 
1314
 
 
1315
 
 
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("|");
 
1321
                                }
 
1322
 
 
1323
                                if(toolbarBuiltInButtons[key].default === true || (options.showIcons && options.showIcons.constructor === Array && options.showIcons.indexOf(key) != -1)) {
 
1324
                                        options.toolbar.push(key);
 
1325
                                }
 
1326
                        }
 
1327
                }
 
1328
        }
 
1329
 
 
1330
 
 
1331
        // Handle status bar
 
1332
        if(!options.hasOwnProperty("status")) {
 
1333
                options.status = ["autosave", "lines", "words", "cursor"];
 
1334
        }
 
1335
 
 
1336
 
 
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);
 
1342
                };
 
1343
        }
 
1344
 
 
1345
 
 
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 || {});
 
1350
 
 
1351
 
 
1352
        // Merging the insertTexts, with the given options
 
1353
        options.insertTexts = extend({}, insertTexts, options.insertTexts || {});
 
1354
 
 
1355
 
 
1356
        // Merging the promptTexts, with the given options
 
1357
        options.promptTexts = promptTexts;
 
1358
 
 
1359
 
 
1360
        // Merging the blockStyles, with the given options
 
1361
        options.blockStyles = extend({}, blockStyles, options.blockStyles || {});
 
1362
 
 
1363
 
 
1364
        // Merging the shortcuts, with the given options
 
1365
        options.shortcuts = extend({}, shortcuts, options.shortcuts || {});
 
1366
 
 
1367
 
 
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;
 
1371
 
 
1372
 
 
1373
        // Update this options
 
1374
        this.options = options;
 
1375
 
 
1376
 
 
1377
        // Auto render
 
1378
        this.render();
 
1379
 
 
1380
 
 
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);
 
1386
        }
 
1387
}
 
1388
 
 
1389
/**
 
1390
 * Default markdown render.
 
1391
 */
 
1392
SimpleMDE.prototype.markdown = function(text) {
 
1393
        if(marked) {
 
1394
                // Initialize
 
1395
                var markedOptions = {};
 
1396
 
 
1397
 
 
1398
                // Update options
 
1399
                if(this.options && this.options.renderingConfig && this.options.renderingConfig.singleLineBreaks === false) {
 
1400
                        markedOptions.breaks = false;
 
1401
                } else {
 
1402
                        markedOptions.breaks = true;
 
1403
                }
 
1404
 
 
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;
 
1408
                        };
 
1409
                }
 
1410
 
 
1411
 
 
1412
                // Set options
 
1413
                marked.setOptions(markedOptions);
 
1414
 
 
1415
 
 
1416
                // Return
 
1417
                return marked(text);
 
1418
        }
 
1419
};
 
1420
 
 
1421
/**
 
1422
 * Render editor to the given element.
 
1423
 */
 
1424
SimpleMDE.prototype.render = function(el) {
 
1425
        if(!el) {
 
1426
                el = this.element || document.getElementsByTagName("textarea")[0];
 
1427
        }
 
1428
 
 
1429
        if(this._rendered && this._rendered === el) {
 
1430
                // Already rendered.
 
1431
                return;
 
1432
        }
 
1433
 
 
1434
        this.element = el;
 
1435
        var options = this.options;
 
1436
 
 
1437
        var self = this;
 
1438
        var keyMaps = {};
 
1439
 
 
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) {
 
1443
                        (function(key) {
 
1444
                                keyMaps[fixShortcut(options.shortcuts[key])] = function() {
 
1445
                                        bindings[key](self);
 
1446
                                };
 
1447
                        })(key);
 
1448
                }
 
1449
        }
 
1450
 
 
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);
 
1456
        };
 
1457
 
 
1458
        document.addEventListener("keydown", function(e) {
 
1459
                e = e || window.event;
 
1460
 
 
1461
                if(e.keyCode == 27) {
 
1462
                        if(self.codemirror.getOption("fullScreen")) toggleFullScreen(self);
 
1463
                }
 
1464
        }, false);
 
1465
 
 
1466
        var mode, backdrop;
 
1467
        if(options.spellChecker !== false) {
 
1468
                mode = "spell-checker";
 
1469
                backdrop = options.parsingConfig;
 
1470
                backdrop.name = "gfm";
 
1471
                backdrop.gitHubSpice = false;
 
1472
 
 
1473
                CodeMirrorSpellChecker({
 
1474
                        codeMirrorInstance: CodeMirror
 
1475
                });
 
1476
        } else {
 
1477
                mode = options.parsingConfig;
 
1478
                mode.name = "gfm";
 
1479
                mode.gitHubSpice = false;
 
1480
        }
 
1481
 
 
1482
        this.codemirror = CodeMirror.fromTextArea(el, {
 
1483
                mode: mode,
 
1484
                backdrop: backdrop,
 
1485
                theme: "paper",
 
1486
                tabSize: (options.tabSize != undefined) ? options.tabSize : 2,
 
1487
                indentUnit: (options.tabSize != undefined) ? options.tabSize : 2,
 
1488
                indentWithTabs: (options.indentWithTabs === false) ? false : true,
 
1489
                lineNumbers: false,
 
1490
                autofocus: (options.autofocus === true) ? true : false,
 
1491
                extraKeys: keyMaps,
 
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
 
1496
        });
 
1497
 
 
1498
        if(options.forceSync === true) {
 
1499
                var cm = this.codemirror;
 
1500
                cm.on("change", function() {
 
1501
                        cm.save();
 
1502
                });
 
1503
        }
 
1504
 
 
1505
        this.gui = {};
 
1506
 
 
1507
        if(options.toolbar !== false) {
 
1508
                this.gui.toolbar = this.createToolbar();
 
1509
        }
 
1510
        if(options.status !== false) {
 
1511
                this.gui.statusbar = this.createStatusbar();
 
1512
        }
 
1513
        if(options.autosave != undefined && options.autosave.enabled === true) {
 
1514
                this.autosave();
 
1515
        }
 
1516
 
 
1517
        this.gui.sideBySide = this.createSideBySide();
 
1518
 
 
1519
        this._rendered = this.element;
 
1520
 
 
1521
 
 
1522
        // Fixes CodeMirror bug (#344)
 
1523
        var temp_cm = this.codemirror;
 
1524
        setTimeout(function() {
 
1525
                temp_cm.refresh();
 
1526
        }.bind(temp_cm), 0);
 
1527
};
 
1528
 
 
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") {
 
1532
                try {
 
1533
                        localStorage.setItem("smde_localStorage", 1);
 
1534
                        localStorage.removeItem("smde_localStorage");
 
1535
                } catch(e) {
 
1536
                        return false;
 
1537
                }
 
1538
        } else {
 
1539
                return false;
 
1540
        }
 
1541
 
 
1542
        return true;
 
1543
}
 
1544
 
 
1545
SimpleMDE.prototype.autosave = function() {
 
1546
        if(isLocalStorageAvailable()) {
 
1547
                var simplemde = this;
 
1548
 
 
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");
 
1551
                        return;
 
1552
                }
 
1553
 
 
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);
 
1557
                        });
 
1558
                }
 
1559
 
 
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;
 
1564
                        }
 
1565
 
 
1566
                        this.options.autosave.loaded = true;
 
1567
                }
 
1568
 
 
1569
                localStorage.setItem("smde_" + this.options.autosave.uniqueId, simplemde.value());
 
1570
 
 
1571
                var el = document.getElementById("autosaved");
 
1572
                if(el != null && el != undefined && el != "") {
 
1573
                        var d = new Date();
 
1574
                        var hh = d.getHours();
 
1575
                        var m = d.getMinutes();
 
1576
                        var dd = "am";
 
1577
                        var h = hh;
 
1578
                        if(h >= 12) {
 
1579
                                h = hh - 12;
 
1580
                                dd = "pm";
 
1581
                        }
 
1582
                        if(h == 0) {
 
1583
                                h = 12;
 
1584
                        }
 
1585
                        m = m < 10 ? "0" + m : m;
 
1586
 
 
1587
                        el.innerHTML = "Autosaved: " + h + ":" + m + " " + dd;
 
1588
                }
 
1589
 
 
1590
                this.autosaveTimeoutId = setTimeout(function() {
 
1591
                        simplemde.autosave();
 
1592
                }, this.options.autosave.delay || 10000);
 
1593
        } else {
 
1594
                console.log("SimpleMDE: localStorage not available, cannot autosave");
 
1595
        }
 
1596
};
 
1597
 
 
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");
 
1602
                        return;
 
1603
                }
 
1604
 
 
1605
                localStorage.removeItem("smde_" + this.options.autosave.uniqueId);
 
1606
        } else {
 
1607
                console.log("SimpleMDE: localStorage not available, cannot autosave");
 
1608
        }
 
1609
};
 
1610
 
 
1611
SimpleMDE.prototype.createSideBySide = function() {
 
1612
        var cm = this.codemirror;
 
1613
        var wrapper = cm.getWrapperElement();
 
1614
        var preview = wrapper.nextSibling;
 
1615
 
 
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);
 
1620
        }
 
1621
 
 
1622
        // Syncs scroll  editor -> preview
 
1623
        var cScroll = false;
 
1624
        var pScroll = false;
 
1625
        cm.on("scroll", function(v) {
 
1626
                if(cScroll) {
 
1627
                        cScroll = false;
 
1628
                        return;
 
1629
                }
 
1630
                pScroll = true;
 
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;
 
1635
        });
 
1636
 
 
1637
        // Syncs scroll  preview -> editor
 
1638
        preview.onscroll = function() {
 
1639
                if(pScroll) {
 
1640
                        pScroll = false;
 
1641
                        return;
 
1642
                }
 
1643
                cScroll = true;
 
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);
 
1648
        };
 
1649
        return preview;
 
1650
};
 
1651
 
 
1652
SimpleMDE.prototype.createToolbar = function(items) {
 
1653
        items = items || this.options.toolbar;
 
1654
 
 
1655
        if(!items || items.length === 0) {
 
1656
                return;
 
1657
        }
 
1658
        var i;
 
1659
        for(i = 0; i < items.length; i++) {
 
1660
                if(toolbarBuiltInButtons[items[i]] != undefined) {
 
1661
                        items[i] = toolbarBuiltInButtons[items[i]];
 
1662
                }
 
1663
        }
 
1664
 
 
1665
        var bar = document.createElement("div");
 
1666
        bar.className = "editor-toolbar";
 
1667
 
 
1668
        var self = this;
 
1669
 
 
1670
        var toolbarData = {};
 
1671
        self.toolbar = items;
 
1672
 
 
1673
        for(i = 0; i < items.length; i++) {
 
1674
                if(items[i].name == "guide" && self.options.toolbarGuideIcon === false)
 
1675
                        continue;
 
1676
 
 
1677
                if(self.options.hideIcons && self.options.hideIcons.indexOf(items[i].name) != -1)
 
1678
                        continue;
 
1679
 
 
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())
 
1683
                        continue;
 
1684
 
 
1685
 
 
1686
                // Don't include trailing separators
 
1687
                if(items[i] === "|") {
 
1688
                        var nonSeparatorIconsFollow = false;
 
1689
 
 
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;
 
1693
                                }
 
1694
                        }
 
1695
 
 
1696
                        if(!nonSeparatorIconsFollow)
 
1697
                                continue;
 
1698
                }
 
1699
 
 
1700
 
 
1701
                // Create the icon and append to the toolbar
 
1702
                (function(item) {
 
1703
                        var el;
 
1704
                        if(item === "|") {
 
1705
                                el = createSep();
 
1706
                        } else {
 
1707
                                el = createIcon(item, self.options.toolbarTips, self.options.shortcuts);
 
1708
                        }
 
1709
 
 
1710
                        // bind events, special for info
 
1711
                        if(item.action) {
 
1712
                                if(typeof item.action === "function") {
 
1713
                                        el.onclick = function(e) {
 
1714
                                                e.preventDefault();
 
1715
                                                item.action(self);
 
1716
                                        };
 
1717
                                } else if(typeof item.action === "string") {
 
1718
                                        el.href = item.action;
 
1719
                                        el.target = "_blank";
 
1720
                                }
 
1721
                        }
 
1722
 
 
1723
                        toolbarData[item.name || item] = el;
 
1724
                        bar.appendChild(el);
 
1725
                })(items[i]);
 
1726
        }
 
1727
 
 
1728
        self.toolbarElements = toolbarData;
 
1729
 
 
1730
        var cm = this.codemirror;
 
1731
        cm.on("cursorActivity", function() {
 
1732
                var stat = getState(cm);
 
1733
 
 
1734
                for(var key in toolbarData) {
 
1735
                        (function(key) {
 
1736
                                var el = toolbarData[key];
 
1737
                                if(stat[key]) {
 
1738
                                        el.className += " active";
 
1739
                                } else if(key != "fullscreen" && key != "side-by-side") {
 
1740
                                        el.className = el.className.replace(/\s*active\s*/g, "");
 
1741
                                }
 
1742
                        })(key);
 
1743
                }
 
1744
        });
 
1745
 
 
1746
        var cmWrapper = cm.getWrapperElement();
 
1747
        cmWrapper.parentNode.insertBefore(bar, cmWrapper);
 
1748
        return bar;
 
1749
};
 
1750
 
 
1751
SimpleMDE.prototype.createStatusbar = function(status) {
 
1752
        // Initialize
 
1753
        status = status || this.options.status;
 
1754
        var options = this.options;
 
1755
        var cm = this.codemirror;
 
1756
 
 
1757
 
 
1758
        // Make sure the status variable is valid
 
1759
        if(!status || status.length === 0)
 
1760
                return;
 
1761
 
 
1762
 
 
1763
        // Set up the built-in items
 
1764
        var items = [];
 
1765
        var i, onUpdate, defaultValue;
 
1766
 
 
1767
        for(i = 0; i < status.length; i++) {
 
1768
                // Reset some values
 
1769
                onUpdate = undefined;
 
1770
                defaultValue = undefined;
 
1771
 
 
1772
 
 
1773
                // Handle if custom or not
 
1774
                if(typeof status[i] === "object") {
 
1775
                        items.push({
 
1776
                                className: status[i].className,
 
1777
                                defaultValue: status[i].defaultValue,
 
1778
                                onUpdate: status[i].onUpdate
 
1779
                        });
 
1780
                } else {
 
1781
                        var name = status[i];
 
1782
 
 
1783
                        if(name === "words") {
 
1784
                                defaultValue = function(el) {
 
1785
                                        el.innerHTML = wordCount(cm.getValue());
 
1786
                                };
 
1787
                                onUpdate = function(el) {
 
1788
                                        el.innerHTML = wordCount(cm.getValue());
 
1789
                                };
 
1790
                        } else if(name === "lines") {
 
1791
                                defaultValue = function(el) {
 
1792
                                        el.innerHTML = cm.lineCount();
 
1793
                                };
 
1794
                                onUpdate = function(el) {
 
1795
                                        el.innerHTML = cm.lineCount();
 
1796
                                };
 
1797
                        } else if(name === "cursor") {
 
1798
                                defaultValue = function(el) {
 
1799
                                        el.innerHTML = "0:0";
 
1800
                                };
 
1801
                                onUpdate = function(el) {
 
1802
                                        var pos = cm.getCursor();
 
1803
                                        el.innerHTML = pos.line + ":" + pos.ch;
 
1804
                                };
 
1805
                        } else if(name === "autosave") {
 
1806
                                defaultValue = function(el) {
 
1807
                                        if(options.autosave != undefined && options.autosave.enabled === true) {
 
1808
                                                el.setAttribute("id", "autosaved");
 
1809
                                        }
 
1810
                                };
 
1811
                        }
 
1812
 
 
1813
                        items.push({
 
1814
                                className: name,
 
1815
                                defaultValue: defaultValue,
 
1816
                                onUpdate: onUpdate
 
1817
                        });
 
1818
                }
 
1819
        }
 
1820
 
 
1821
 
 
1822
        // Create element for the status bar
 
1823
        var bar = document.createElement("div");
 
1824
        bar.className = "editor-statusbar";
 
1825
 
 
1826
 
 
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];
 
1831
 
 
1832
 
 
1833
                // Create span element
 
1834
                var el = document.createElement("span");
 
1835
                el.className = item.className;
 
1836
 
 
1837
 
 
1838
                // Ensure the defaultValue is a function
 
1839
                if(typeof item.defaultValue === "function") {
 
1840
                        item.defaultValue(el);
 
1841
                }
 
1842
 
 
1843
 
 
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) {
 
1848
                                return function() {
 
1849
                                        item.onUpdate(el);
 
1850
                                };
 
1851
                        }(el, item)));
 
1852
                }
 
1853
 
 
1854
 
 
1855
                // Append the item to the status bar
 
1856
                bar.appendChild(el);
 
1857
        }
 
1858
 
 
1859
 
 
1860
        // Insert the status bar into the DOM
 
1861
        var cmWrapper = this.codemirror.getWrapperElement();
 
1862
        cmWrapper.parentNode.insertBefore(bar, cmWrapper.nextSibling);
 
1863
        return bar;
 
1864
};
 
1865
 
 
1866
/**
 
1867
 * Get or set the text content.
 
1868
 */
 
1869
SimpleMDE.prototype.value = function(val) {
 
1870
        if(val === undefined) {
 
1871
                return this.codemirror.getValue();
 
1872
        } else {
 
1873
                this.codemirror.getDoc().setValue(val);
 
1874
                return this;
 
1875
        }
 
1876
};
 
1877
 
 
1878
 
 
1879
/**
 
1880
 * Bind static methods for exports.
 
1881
 */
 
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;
 
1904
 
 
1905
/**
 
1906
 * Bind instance methods for exports.
 
1907
 */
 
1908
SimpleMDE.prototype.toggleBold = function() {
 
1909
        toggleBold(this);
 
1910
};
 
1911
SimpleMDE.prototype.toggleItalic = function() {
 
1912
        toggleItalic(this);
 
1913
};
 
1914
SimpleMDE.prototype.toggleStrikethrough = function() {
 
1915
        toggleStrikethrough(this);
 
1916
};
 
1917
SimpleMDE.prototype.toggleBlockquote = function() {
 
1918
        toggleBlockquote(this);
 
1919
};
 
1920
SimpleMDE.prototype.toggleHeadingSmaller = function() {
 
1921
        toggleHeadingSmaller(this);
 
1922
};
 
1923
SimpleMDE.prototype.toggleHeadingBigger = function() {
 
1924
        toggleHeadingBigger(this);
 
1925
};
 
1926
SimpleMDE.prototype.toggleHeading1 = function() {
 
1927
        toggleHeading1(this);
 
1928
};
 
1929
SimpleMDE.prototype.toggleHeading2 = function() {
 
1930
        toggleHeading2(this);
 
1931
};
 
1932
SimpleMDE.prototype.toggleHeading3 = function() {
 
1933
        toggleHeading3(this);
 
1934
};
 
1935
SimpleMDE.prototype.toggleCodeBlock = function() {
 
1936
        toggleCodeBlock(this);
 
1937
};
 
1938
SimpleMDE.prototype.toggleUnorderedList = function() {
 
1939
        toggleUnorderedList(this);
 
1940
};
 
1941
SimpleMDE.prototype.toggleOrderedList = function() {
 
1942
        toggleOrderedList(this);
 
1943
};
 
1944
SimpleMDE.prototype.cleanBlock = function() {
 
1945
        cleanBlock(this);
 
1946
};
 
1947
SimpleMDE.prototype.drawLink = function() {
 
1948
        drawLink(this);
 
1949
};
 
1950
SimpleMDE.prototype.drawImage = function() {
 
1951
        drawImage(this);
 
1952
};
 
1953
SimpleMDE.prototype.drawTable = function() {
 
1954
        drawTable(this);
 
1955
};
 
1956
SimpleMDE.prototype.drawHorizontalRule = function() {
 
1957
        drawHorizontalRule(this);
 
1958
};
 
1959
SimpleMDE.prototype.undo = function() {
 
1960
        undo(this);
 
1961
};
 
1962
SimpleMDE.prototype.redo = function() {
 
1963
        redo(this);
 
1964
};
 
1965
SimpleMDE.prototype.togglePreview = function() {
 
1966
        togglePreview(this);
 
1967
};
 
1968
SimpleMDE.prototype.toggleSideBySide = function() {
 
1969
        toggleSideBySide(this);
 
1970
};
 
1971
SimpleMDE.prototype.toggleFullScreen = function() {
 
1972
        toggleFullScreen(this);
 
1973
};
 
1974
 
 
1975
SimpleMDE.prototype.isPreviewActive = function() {
 
1976
        var cm = this.codemirror;
 
1977
        var wrapper = cm.getWrapperElement();
 
1978
        var preview = wrapper.lastChild;
 
1979
 
 
1980
        return /editor-preview-active/.test(preview.className);
 
1981
};
 
1982
 
 
1983
SimpleMDE.prototype.isSideBySideActive = function() {
 
1984
        var cm = this.codemirror;
 
1985
        var wrapper = cm.getWrapperElement();
 
1986
        var preview = wrapper.nextSibling;
 
1987
 
 
1988
        return /editor-preview-active-side/.test(preview.className);
 
1989
};
 
1990
 
 
1991
SimpleMDE.prototype.isFullscreenActive = function() {
 
1992
        var cm = this.codemirror;
 
1993
 
 
1994
        return cm.getOption("fullScreen");
 
1995
};
 
1996
 
 
1997
SimpleMDE.prototype.getState = function() {
 
1998
        var cm = this.codemirror;
 
1999
 
 
2000
        return getState(cm);
 
2001
};
 
2002
 
 
2003
SimpleMDE.prototype.toTextArea = function() {
 
2004
        var cm = this.codemirror;
 
2005
        var wrapper = cm.getWrapperElement();
 
2006
 
 
2007
        if(wrapper.parentNode) {
 
2008
                if(this.gui.toolbar) {
 
2009
                        wrapper.parentNode.removeChild(this.gui.toolbar);
 
2010
                }
 
2011
                if(this.gui.statusbar) {
 
2012
                        wrapper.parentNode.removeChild(this.gui.statusbar);
 
2013
                }
 
2014
                if(this.gui.sideBySide) {
 
2015
                        wrapper.parentNode.removeChild(this.gui.sideBySide);
 
2016
                }
 
2017
        }
 
2018
 
 
2019
        cm.toTextArea();
 
2020
 
 
2021
        if(this.autosaveTimeoutId) {
 
2022
                clearTimeout(this.autosaveTimeoutId);
 
2023
                this.autosaveTimeoutId = undefined;
 
2024
                this.clearAutosavedValue();
 
2025
        }
 
2026
};
 
2027
 
 
2028
module.exports = SimpleMDE;