懒羊羊
2023-08-30 1ac2bc1590406d9babec036e154d8d08f34a6aa1
提交 | 用户 | 时间
1ac2bc 1 ;(function(factory) {
2     "use strict";
3     
4     // CommonJS/Node.js
5     if (typeof require === "function" && typeof exports === "object" && typeof module === "object")
6     { 
7         module.exports = factory;
8     }
9     else if (typeof define === "function")  // AMD/CMD/Sea.js
10     {
11         if (define.amd) // for Require.js
12         {
13             /* Require.js define replace */
14         } 
15         else 
16         {
17             define(["jquery"], factory);  // for Sea.js
18         }
19     } 
20     else
21     { 
22         window.editormd = factory();
23     }
24     
25 }(function() {    
26
27     /* Require.js assignment replace */
28     
29     "use strict";
30     
31     var $ = (typeof (jQuery) !== "undefined") ? jQuery : Zepto;
32
33     if (typeof ($) === "undefined") {
34         return ;
35     }
36     
37     /**
38      * editormd
39      * 
40      * @param   {String} id           编辑器的ID
41      * @param   {Object} options      配置选项 Key/Value
42      * @returns {Object} editormd     返回editormd对象
43      */
44     
45     var editormd         = function (id, options) {
46         return new editormd.fn.init(id, options);
47     };
48     
49     editormd.title        = editormd.$name = "Editor.md";
50     editormd.version      = "1.5.0";
51     editormd.homePage     = "https://pandao.github.io/editor.md/";
52     editormd.classPrefix  = "editormd-";
53     
54     editormd.toolbarModes = {
55         full : [
56             "undo", "redo", "|", 
57             "bold", "del", "italic", "quote", "ucwords", "uppercase", "lowercase", "|", 
58             "h1", "h2", "h3", "h4", "h5", "h6", "|", 
59             "list-ul", "list-ol", "hr", "|",
60             "link", "reference-link", "image", "code", "preformatted-text", "code-block", "table", "datetime", "emoji", "html-entities", "pagebreak", "|",
61             "goto-line", "watch", "preview", "fullscreen", "clear", "search", "|",
62             "help", "info"
63         ],
64         simple : [
65             "undo", "redo", "|", 
66             "bold", "del", "italic", "quote", "uppercase", "lowercase", "|", 
67             "h1", "h2", "h3", "h4", "h5", "h6", "|", 
68             "list-ul", "list-ol", "hr", "|",
69             "watch", "preview", "fullscreen", "|",
70             "help", "info"
71         ],
72         mini : [
73             "undo", "redo", "|",
74             "watch", "preview", "|",
75             "help", "info"
76         ]
77     };
78     
79     editormd.defaults     = {
80         mode                 : "gfm",          //gfm or markdown
81         name                 : "",             // Form element name
82         value                : "",             // value for CodeMirror, if mode not gfm/markdown
83         theme                : "",             // Editor.md self themes, before v1.5.0 is CodeMirror theme, default empty
84         editorTheme          : "default",      // Editor area, this is CodeMirror theme at v1.5.0
85         previewTheme         : "",             // Preview area theme, default empty
86         markdown             : "",             // Markdown source code
87         appendMarkdown       : "",             // if in init textarea value not empty, append markdown to textarea
88         width                : "100%",
89         height               : "100%",
90         path                 : "./lib/",       // Dependents module file directory
91         pluginPath           : "",             // If this empty, default use settings.path + "../plugins/"
92         delay                : 300,            // Delay parse markdown to html, Uint : ms
93         autoLoadModules      : true,           // Automatic load dependent module files
94         watch                : true,
95         placeholder          : "Enjoy Markdown! coding now...",
96         gotoLine             : true,
97         codeFold             : false,
98         autoHeight           : false,
99         autoFocus            : true,
100         autoCloseTags        : true,
101         searchReplace        : true,
102         syncScrolling        : true,           // true | false | "single", default true
103         readOnly             : false,
104         tabSize              : 4,
105         indentUnit           : 4,
106         lineNumbers          : true,
107         lineWrapping         : true,
108         autoCloseBrackets    : true,
109         showTrailingSpace    : true,
110         matchBrackets        : true,
111         indentWithTabs       : true,
112         styleSelectedText    : true,
113         matchWordHighlight   : true,           // options: true, false, "onselected"
114         styleActiveLine      : true,           // Highlight the current line
115         dialogLockScreen     : true,
116         dialogShowMask       : true,
117         dialogDraggable      : true,
118         dialogMaskBgColor    : "#fff",
119         dialogMaskOpacity    : 0.1,
120         fontSize             : "13px",
121         saveHTMLToTextarea   : false,
122         disabledKeyMaps      : [],
123         
124         onload               : function() {},
125         onresize             : function() {},
126         onchange             : function() {},
127         onwatch              : null,
128         onunwatch            : null,
129         onpreviewing         : function() {},
130         onpreviewed          : function() {},
131         onfullscreen         : function() {},
132         onfullscreenExit     : function() {},
133         onscroll             : function() {},
134         onpreviewscroll      : function() {},
135         
136         imageUpload          : false,
137         imageFormats         : ["jpg", "jpeg", "gif", "png", "bmp", "webp"],
138         imageUploadURL       : "",
139         crossDomainUpload    : false,
140         uploadCallbackURL    : "",
141         
142         toc                  : true,           // Table of contents
143         tocm                 : false,           // Using [TOCM], auto create ToC dropdown menu
144         tocTitle             : "",             // for ToC dropdown menu btn
145         tocDropdown          : false,
146         tocContainer         : "",
147         tocStartLevel        : 1,              // Said from H1 to create ToC
148         htmlDecode           : false,          // Open the HTML tag identification 
149         pageBreak            : true,           // Enable parse page break [========]
150         atLink               : true,           // for @link
151         emailLink            : true,           // for email address auto link
152         taskList             : false,          // Enable Github Flavored Markdown task lists
153         emoji                : false,          // :emoji: , Support Github emoji, Twitter Emoji (Twemoji);
154                                                // Support FontAwesome icon emoji :fa-xxx: > Using fontAwesome icon web fonts;
155                                                // Support Editor.md logo icon emoji :editormd-logo: :editormd-logo-1x: > 1~8x;
156         tex                  : false,          // TeX(LaTeX), based on KaTeX
157         flowChart            : false,          // flowChart.js only support IE9+
158         sequenceDiagram      : false,          // sequenceDiagram.js only support IE9+
159         previewCodeHighlight : true,
160                 
161         toolbar              : true,           // show/hide toolbar
162         toolbarAutoFixed     : true,           // on window scroll auto fixed position
163         toolbarIcons         : "full",
164         toolbarTitles        : {},
165         toolbarHandlers      : {
166             ucwords : function() {
167                 return editormd.toolbarHandlers.ucwords;
168             },
169             lowercase : function() {
170                 return editormd.toolbarHandlers.lowercase;
171             }
172         },
173         toolbarCustomIcons   : {               // using html tag create toolbar icon, unused default <a> tag.
174             lowercase        : "<a href=\"javascript:;\" title=\"Lowercase\" unselectable=\"on\"><i class=\"fa\" name=\"lowercase\" style=\"font-size:24px;margin-top: -10px;\">a</i></a>",
175             "ucwords"        : "<a href=\"javascript:;\" title=\"ucwords\" unselectable=\"on\"><i class=\"fa\" name=\"ucwords\" style=\"font-size:20px;margin-top: -3px;\">Aa</i></a>"
176         }, 
177         toolbarIconsClass    : {
178             undo             : "fa-undo",
179             redo             : "fa-repeat",
180             bold             : "fa-bold",
181             del              : "fa-strikethrough",
182             italic           : "fa-italic",
183             quote            : "fa-quote-left",
184             uppercase        : "fa-font",
185             h1               : editormd.classPrefix + "bold",
186             h2               : editormd.classPrefix + "bold",
187             h3               : editormd.classPrefix + "bold",
188             h4               : editormd.classPrefix + "bold",
189             h5               : editormd.classPrefix + "bold",
190             h6               : editormd.classPrefix + "bold",
191             "list-ul"        : "fa-list-ul",
192             "list-ol"        : "fa-list-ol",
193             hr               : "fa-minus",
194             link             : "fa-link",
195             "reference-link" : "fa-anchor",
196             image            : "fa-picture-o",
197             code             : "fa-code",
198             "preformatted-text" : "fa-file-code-o",
199             "code-block"     : "fa-file-code-o",
200             table            : "fa-table",
201             datetime         : "fa-clock-o",
202             emoji            : "fa-smile-o",
203             "html-entities"  : "fa-copyright",
204             pagebreak        : "fa-newspaper-o",
205             "goto-line"      : "fa-terminal", // fa-crosshairs
206             watch            : "fa-eye-slash",
207             unwatch          : "fa-eye",
208             preview          : "fa-desktop",
209             search           : "fa-search",
210             fullscreen       : "fa-arrows-alt",
211             clear            : "fa-eraser",
212             help             : "fa-question-circle",
213             info             : "fa-info-circle"
214         },        
215         toolbarIconTexts     : {},
216         
217         lang : {
218             name        : "zh-cn",
219             description : "开源在线Markdown编辑器<br/>Open source online Markdown editor.",
220             tocTitle    : "目录",
221             toolbar     : {
222                 undo             : "撤销(Ctrl+Z)",
223                 redo             : "重做(Ctrl+Y)",
224                 bold             : "粗体",
225                 del              : "删除线",
226                 italic           : "斜体",
227                 quote            : "引用",
228                 ucwords          : "将每个单词首字母转成大写",
229                 uppercase        : "将所选转换成大写",
230                 lowercase        : "将所选转换成小写",
231                 h1               : "标题1",
232                 h2               : "标题2",
233                 h3               : "标题3",
234                 h4               : "标题4",
235                 h5               : "标题5",
236                 h6               : "标题6",
237                 "list-ul"        : "无序列表",
238                 "list-ol"        : "有序列表",
239                 hr               : "横线",
240                 link             : "链接",
241                 "reference-link" : "引用链接",
242                 image            : "添加图片",
243                 code             : "行内代码",
244                 "preformatted-text" : "预格式文本 / 代码块(缩进风格)",
245                 "code-block"     : "代码块(多语言风格)",
246                 table            : "添加表格",
247                 datetime         : "日期时间",
248                 emoji            : "Emoji表情",
249                 "html-entities"  : "HTML实体字符",
250                 pagebreak        : "插入分页符",
251                 "goto-line"      : "跳转到行",
252                 watch            : "关闭实时预览",
253                 unwatch          : "开启实时预览",
254                 preview          : "全窗口预览HTML(按 Shift + ESC还原)",
255                 fullscreen       : "全屏(按ESC还原)",
256                 clear            : "清空",
257                 search           : "搜索",
258                 help             : "使用帮助",
259                 info             : "关于" + editormd.title
260             },
261             buttons : {
262                 enter  : "确定",
263                 cancel : "取消",
264                 close  : "关闭"
265             },
266             dialog : {
267                 link : {
268                     title    : "添加链接",
269                     url      : "链接地址",
270                     urlTitle : "链接标题",
271                     urlEmpty : "错误:请填写链接地址。"
272                 },
273                 referenceLink : {
274                     title    : "添加引用链接",
275                     name     : "引用名称",
276                     url      : "链接地址",
277                     urlId    : "链接ID",
278                     urlTitle : "链接标题",
279                     nameEmpty: "错误:引用链接的名称不能为空。",
280                     idEmpty  : "错误:请填写引用链接的ID。",
281                     urlEmpty : "错误:请填写引用链接的URL地址。"
282                 },
283                 image : {
284                     title    : "添加图片",
285                     url      : "图片地址",
286                     link     : "图片链接",
287                     alt      : "图片描述",
288                     uploadButton     : "本地上传",
289                     imageURLEmpty    : "错误:图片地址不能为空。",
290                     uploadFileEmpty  : "错误:上传的图片不能为空。",
291                     formatNotAllowed : "错误:只允许上传图片文件,允许上传的图片文件格式有:"
292                 },
293                 preformattedText : {
294                     title             : "添加预格式文本或代码块", 
295                     emptyAlert        : "错误:请填写预格式文本或代码的内容。"
296                 },
297                 codeBlock : {
298                     title             : "添加代码块",                    
299                     selectLabel       : "代码语言:",
300                     selectDefaultText : "请选择代码语言",
301                     otherLanguage     : "其他语言",
302                     unselectedLanguageAlert : "错误:请选择代码所属的语言类型。",
303                     codeEmptyAlert    : "错误:请填写代码内容。"
304                 },
305                 htmlEntities : {
306                     title : "HTML 实体字符"
307                 },
308                 help : {
309                     title : "使用帮助"
310                 }
311             }
312         }
313     };
314     
315     editormd.classNames  = {
316         tex : editormd.classPrefix + "tex"
317     };
318
319     editormd.dialogZindex = 99999;
320     
321     editormd.$katex       = null;
322     editormd.$marked      = null;
323     editormd.$CodeMirror  = null;
324     editormd.$prettyPrint = null;
325     
326     var timer, flowchartTimer;
327
328     editormd.prototype    = editormd.fn = {
329         state : {
330             watching   : false,
331             loaded     : false,
332             preview    : false,
333             fullscreen : false
334         },
335         
336         /**
337          * 构造函数/实例初始化
338          * Constructor / instance initialization
339          * 
340          * @param   {String}   id            编辑器的ID
341          * @param   {Object}   [options={}]  配置选项 Key/Value
342          * @returns {editormd}               返回editormd的实例对象
343          */
344         
345         init : function (id, options) {
346             
347             options              = options || {};
348             
349             if (typeof id === "object")
350             {
351                 options = id;
352             }
353             
354             var _this            = this;
355             var classPrefix      = this.classPrefix  = editormd.classPrefix; 
356             var settings         = this.settings     = $.extend(true, editormd.defaults, options);
357             
358             id                   = (typeof id === "object") ? settings.id : id;
359             
360             var editor           = this.editor       = $("#" + id);
361             
362             this.id              = id;
363             this.lang            = settings.lang;
364             
365             var classNames       = this.classNames   = {
366                 textarea : {
367                     html     : classPrefix + "html-textarea",
368                     markdown : classPrefix + "markdown-textarea"
369                 }
370             };
371             
372             settings.pluginPath = (settings.pluginPath === "") ? settings.path + "../plugins/" : settings.pluginPath; 
373             
374             this.state.watching = (settings.watch) ? true : false;
375             
376             if ( !editor.hasClass("editormd") ) {
377                 editor.addClass("editormd");
378             }
379             
380             editor.css({
381                 width  : (typeof settings.width  === "number") ? settings.width  + "px" : settings.width,
382                 height : (typeof settings.height === "number") ? settings.height + "px" : settings.height
383             });
384             
385             if (settings.autoHeight)
386             {
387                 editor.css("height", "auto");
388             }
389                         
390             var markdownTextarea = this.markdownTextarea = editor.children("textarea");
391             
392             if (markdownTextarea.length < 1)
393             {
394                 editor.append("<textarea></textarea>");
395                 markdownTextarea = this.markdownTextarea = editor.children("textarea");
396             }
397             
398             markdownTextarea.addClass(classNames.textarea.markdown).attr("placeholder", settings.placeholder);
399             
400             if (typeof markdownTextarea.attr("name") === "undefined" || markdownTextarea.attr("name") === "")
401             {
402                 markdownTextarea.attr("name", (settings.name !== "") ? settings.name : id + "-markdown-doc");
403             }
404             
405             var appendElements = [
406                 (!settings.readOnly) ? "<a href=\"javascript:;\" class=\"fa fa-close " + classPrefix + "preview-close-btn\"></a>" : "",
407                 ( (settings.saveHTMLToTextarea) ? "<textarea class=\"" + classNames.textarea.html + "\" name=\"" + id + "-html-code\"></textarea>" : "" ),
408                 "<div class=\"" + classPrefix + "preview\"><div class=\"markdown-body " + classPrefix + "preview-container\"></div></div>",
409                 "<div class=\"" + classPrefix + "container-mask\" style=\"display:block;\"></div>",
410                 "<div class=\"" + classPrefix + "mask\"></div>"
411             ].join("\n");
412             
413             editor.append(appendElements).addClass(classPrefix + "vertical");
414             
415             if (settings.theme !== "") 
416             {
417                 editor.addClass(classPrefix + "theme-" + settings.theme);
418             }
419             
420             this.mask          = editor.children("." + classPrefix + "mask");    
421             this.containerMask = editor.children("." + classPrefix  + "container-mask");
422             
423             if (settings.markdown !== "")
424             {
425                 markdownTextarea.val(settings.markdown);
426             }
427             
428             if (settings.appendMarkdown !== "")
429             {
430                 markdownTextarea.val(markdownTextarea.val() + settings.appendMarkdown);
431             }
432             
433             this.htmlTextarea     = editor.children("." + classNames.textarea.html);            
434             this.preview          = editor.children("." + classPrefix + "preview");
435             this.previewContainer = this.preview.children("." + classPrefix + "preview-container");
436             
437             if (settings.previewTheme !== "") 
438             {
439                 this.preview.addClass(classPrefix + "preview-theme-" + settings.previewTheme);
440             }
441             
442             if (typeof define === "function" && define.amd)
443             {
444                 if (typeof katex !== "undefined") 
445                 {
446                     editormd.$katex = katex;
447                 }
448                 
449                 if (settings.searchReplace && !settings.readOnly) 
450                 {
451                     editormd.loadCSS(settings.path + "codemirror/addon/dialog/dialog");
452                     editormd.loadCSS(settings.path + "codemirror/addon/search/matchesonscrollbar");
453                 }
454             }
455             
456             if ((typeof define === "function" && define.amd) || !settings.autoLoadModules)
457             {
458                 if (typeof CodeMirror !== "undefined") {
459                     editormd.$CodeMirror = CodeMirror;
460                 }
461                 
462                 if (typeof marked     !== "undefined") {
463                     editormd.$marked     = marked;
464                 }
465                 
466                 this.setCodeMirror().setToolbar().loadedDisplay();
467             } 
468             else 
469             {
470                 this.loadQueues();
471             }
472
473             return this;
474         },
475         
476         /**
477          * 所需组件加载队列
478          * Required components loading queue
479          * 
480          * @returns {editormd}  返回editormd的实例对象
481          */
482         
483         loadQueues : function() {
484             var _this        = this;
485             var settings     = this.settings;
486             var loadPath     = settings.path;
487                                 
488             var loadFlowChartOrSequenceDiagram = function() {
489                 
490                 if (editormd.isIE8) 
491                 {
492                     _this.loadedDisplay();
493                     
494                     return ;
495                 }
496
497                 if (settings.flowChart || settings.sequenceDiagram) 
498                 {
499                     editormd.loadScript(loadPath + "raphael.min", function() {
500
501                         editormd.loadScript(loadPath + "underscore.min", function() {  
502
503                             if (!settings.flowChart && settings.sequenceDiagram) 
504                             {
505                                 editormd.loadScript(loadPath + "sequence-diagram.min", function() {
506                                     _this.loadedDisplay();
507                                 });
508                             }
509                             else if (settings.flowChart && !settings.sequenceDiagram) 
510                             {      
511                                 editormd.loadScript(loadPath + "flowchart.min", function() {  
512                                     editormd.loadScript(loadPath + "jquery.flowchart.min", function() {
513                                         _this.loadedDisplay();
514                                     });
515                                 });
516                             }
517                             else if (settings.flowChart && settings.sequenceDiagram) 
518                             {  
519                                 editormd.loadScript(loadPath + "flowchart.min", function() {  
520                                     editormd.loadScript(loadPath + "jquery.flowchart.min", function() {
521                                         editormd.loadScript(loadPath + "sequence-diagram.min", function() {
522                                             _this.loadedDisplay();
523                                         });
524                                     });
525                                 });
526                             }
527                         });
528
529                     });
530                 } 
531                 else
532                 {
533                     _this.loadedDisplay();
534                 }
535             }; 
536
537             editormd.loadCSS(loadPath + "codemirror/codemirror.min");
538             
539             if (settings.searchReplace && !settings.readOnly)
540             {
541                 editormd.loadCSS(loadPath + "codemirror/addon/dialog/dialog");
542                 editormd.loadCSS(loadPath + "codemirror/addon/search/matchesonscrollbar");
543             }
544             
545             if (settings.codeFold)
546             {
547                 editormd.loadCSS(loadPath + "codemirror/addon/fold/foldgutter");            
548             }
549             
550             editormd.loadScript(loadPath + "codemirror/codemirror.min", function() {
551                 editormd.$CodeMirror = CodeMirror;
552                 
553                 editormd.loadScript(loadPath + "codemirror/modes.min", function() {
554                     
555                     editormd.loadScript(loadPath + "codemirror/addons.min", function() {
556                         
557                         _this.setCodeMirror();
558                         
559                         if (settings.mode !== "gfm" && settings.mode !== "markdown") 
560                         {
561                             _this.loadedDisplay();
562                             
563                             return false;
564                         }
565                         
566                         _this.setToolbar();
567
568                         editormd.loadScript(loadPath + "marked.min", function() {
569
570                             editormd.$marked = marked;
571                                 
572                             if (settings.previewCodeHighlight) 
573                             {
574                                 editormd.loadScript(loadPath + "prettify.min", function() {
575                                     loadFlowChartOrSequenceDiagram();
576                                 });
577                             } 
578                             else
579                             {                  
580                                 loadFlowChartOrSequenceDiagram();
581                             }
582                         });
583                         
584                     });
585                     
586                 });
587                 
588             });
589
590             return this;
591         },
592         
593         /**
594          * 设置 Editor.md 的整体主题,主要是工具栏
595          * Setting Editor.md theme
596          * 
597          * @returns {editormd}  返回editormd的实例对象
598          */
599         
600         setTheme : function(theme) {
601             var editor      = this.editor;
602             var oldTheme    = this.settings.theme;
603             var themePrefix = this.classPrefix + "theme-";
604             
605             editor.removeClass(themePrefix + oldTheme).addClass(themePrefix + theme);
606             
607             this.settings.theme = theme;
608             
609             return this;
610         },
611         
612         /**
613          * 设置 CodeMirror(编辑区)的主题
614          * Setting CodeMirror (Editor area) theme
615          * 
616          * @returns {editormd}  返回editormd的实例对象
617          */
618         
619         setEditorTheme : function(theme) {  
620             var settings   = this.settings;  
621             settings.editorTheme = theme;  
622             
623             if (theme !== "default")
624             {
625                 editormd.loadCSS(settings.path + "codemirror/theme/" + settings.editorTheme);
626             }
627             
628             this.cm.setOption("theme", theme);
629             
630             return this;
631         },
632         
633         /**
634          * setEditorTheme() 的别名
635          * setEditorTheme() alias
636          * 
637          * @returns {editormd}  返回editormd的实例对象
638          */
639         
640         setCodeMirrorTheme : function (theme) {            
641             this.setEditorTheme(theme);
642             
643             return this;
644         },
645         
646         /**
647          * 设置 Editor.md 的主题
648          * Setting Editor.md theme
649          * 
650          * @returns {editormd}  返回editormd的实例对象
651          */
652         
653         setPreviewTheme : function(theme) {  
654             var preview     = this.preview;
655             var oldTheme    = this.settings.previewTheme;
656             var themePrefix = this.classPrefix + "preview-theme-";
657             
658             preview.removeClass(themePrefix + oldTheme).addClass(themePrefix + theme);
659             
660             this.settings.previewTheme = theme;
661             
662             return this;
663         },
664         
665         /**
666          * 配置和初始化CodeMirror组件
667          * CodeMirror initialization
668          * 
669          * @returns {editormd}  返回editormd的实例对象
670          */
671         
672         setCodeMirror : function() { 
673             var settings         = this.settings;
674             var editor           = this.editor;
675             
676             if (settings.editorTheme !== "default")
677             {
678                 editormd.loadCSS(settings.path + "codemirror/theme/" + settings.editorTheme);
679             }
680             
681             var codeMirrorConfig = {
682                 mode                      : settings.mode,
683                 theme                     : settings.editorTheme,
684                 tabSize                   : settings.tabSize,
685                 dragDrop                  : false,
686                 autofocus                 : settings.autoFocus,
687                 autoCloseTags             : settings.autoCloseTags,
688                 readOnly                  : (settings.readOnly) ? "nocursor" : false,
689                 indentUnit                : settings.indentUnit,
690                 lineNumbers               : settings.lineNumbers,
691                 lineWrapping              : settings.lineWrapping,
692                 extraKeys                 : {
693                                                 "Ctrl-Q": function(cm) { 
694                                                     cm.foldCode(cm.getCursor()); 
695                                                 }
696                                             },
697                 foldGutter                : settings.codeFold,
698                 gutters                   : ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
699                 matchBrackets             : settings.matchBrackets,
700                 indentWithTabs            : settings.indentWithTabs,
701                 styleActiveLine           : settings.styleActiveLine,
702                 styleSelectedText         : settings.styleSelectedText,
703                 autoCloseBrackets         : settings.autoCloseBrackets,
704                 showTrailingSpace         : settings.showTrailingSpace,
705                 highlightSelectionMatches : ( (!settings.matchWordHighlight) ? false : { showToken: (settings.matchWordHighlight === "onselected") ? false : /\w/ } )
706             };
707             
708             this.codeEditor = this.cm        = editormd.$CodeMirror.fromTextArea(this.markdownTextarea[0], codeMirrorConfig);
709             this.codeMirror = this.cmElement = editor.children(".CodeMirror");
710             
711             if (settings.value !== "")
712             {
713                 this.cm.setValue(settings.value);
714             }
715
716             this.codeMirror.css({
717                 fontSize : settings.fontSize,
718                 width    : (!settings.watch) ? "100%" : "50%"
719             });
720             
721             if (settings.autoHeight)
722             {
723                 this.codeMirror.css("height", "auto");
724                 this.cm.setOption("viewportMargin", Infinity);
725             }
726             
727             if (!settings.lineNumbers)
728             {
729                 this.codeMirror.find(".CodeMirror-gutters").css("border-right", "none");
730             }
731
732             return this;
733         },
734         
735         /**
736          * 获取CodeMirror的配置选项
737          * Get CodeMirror setting options
738          * 
739          * @returns {Mixed}                  return CodeMirror setting option value
740          */
741         
742         getCodeMirrorOption : function(key) {            
743             return this.cm.getOption(key);
744         },
745         
746         /**
747          * 配置和重配置CodeMirror的选项
748          * CodeMirror setting options / resettings
749          * 
750          * @returns {editormd}  返回editormd的实例对象
751          */
752         
753         setCodeMirrorOption : function(key, value) {
754             
755             this.cm.setOption(key, value);
756             
757             return this;
758         },
759         
760         /**
761          * 添加 CodeMirror 键盘快捷键
762          * Add CodeMirror keyboard shortcuts key map
763          * 
764          * @returns {editormd}  返回editormd的实例对象
765          */
766         
767         addKeyMap : function(map, bottom) {
768             this.cm.addKeyMap(map, bottom);
769             
770             return this;
771         },
772         
773         /**
774          * 移除 CodeMirror 键盘快捷键
775          * Remove CodeMirror keyboard shortcuts key map
776          * 
777          * @returns {editormd}  返回editormd的实例对象
778          */
779         
780         removeKeyMap : function(map) {
781             this.cm.removeKeyMap(map);
782             
783             return this;
784         },
785         
786         /**
787          * 跳转到指定的行
788          * Goto CodeMirror line
789          * 
790          * @param   {String|Intiger}   line      line number or "first"|"last"
791          * @returns {editormd}                   返回editormd的实例对象
792          */
793         
794         gotoLine : function (line) {
795             
796             var settings = this.settings;
797             
798             if (!settings.gotoLine)
799             {
800                 return this;
801             }
802             
803             var cm       = this.cm;
804             var editor   = this.editor;
805             var count    = cm.lineCount();
806             var preview  = this.preview;
807             
808             if (typeof line === "string")
809             {
810                 if(line === "last")
811                 {
812                     line = count;
813                 }
814             
815                 if (line === "first")
816                 {
817                     line = 1;
818                 }
819             }
820             
821             if (typeof line !== "number") 
822             {  
823                 alert("Error: The line number must be an integer.");
824                 return this;
825             }
826             
827             line  = parseInt(line) - 1;
828             
829             if (line > count)
830             {
831                 alert("Error: The line number range 1-" + count);
832                 
833                 return this;
834             }
835             
836             cm.setCursor( {line : line, ch : 0} );
837             
838             var scrollInfo   = cm.getScrollInfo();
839             var clientHeight = scrollInfo.clientHeight; 
840             var coords       = cm.charCoords({line : line, ch : 0}, "local");
841             
842             cm.scrollTo(null, (coords.top + coords.bottom - clientHeight) / 2);
843             
844             if (settings.watch)
845             {            
846                 var cmScroll  = this.codeMirror.find(".CodeMirror-scroll")[0];
847                 var height    = $(cmScroll).height(); 
848                 var scrollTop = cmScroll.scrollTop;         
849                 var percent   = (scrollTop / cmScroll.scrollHeight);
850
851                 if (scrollTop === 0)
852                 {
853                     preview.scrollTop(0);
854                 } 
855                 else if (scrollTop + height >= cmScroll.scrollHeight - 16)
856                 { 
857                     preview.scrollTop(preview[0].scrollHeight);                    
858                 } 
859                 else
860                 {                    
861                     preview.scrollTop(preview[0].scrollHeight * percent);
862                 }
863             }
864
865             cm.focus();
866             
867             return this;
868         },
869         
870         /**
871          * 扩展当前实例对象,可同时设置多个或者只设置一个
872          * Extend editormd instance object, can mutil setting.
873          * 
874          * @returns {editormd}                  this(editormd instance object.)
875          */
876         
877         extend : function() {
878             if (typeof arguments[1] !== "undefined")
879             {
880                 if (typeof arguments[1] === "function")
881                 {
882                     arguments[1] = $.proxy(arguments[1], this);
883                 }
884
885                 this[arguments[0]] = arguments[1];
886             }
887             
888             if (typeof arguments[0] === "object" && typeof arguments[0].length === "undefined")
889             {
890                 $.extend(true, this, arguments[0]);
891             }
892
893             return this;
894         },
895         
896         /**
897          * 设置或扩展当前实例对象,单个设置
898          * Extend editormd instance object, one by one
899          * 
900          * @param   {String|Object}   key       option key
901          * @param   {String|Object}   value     option value
902          * @returns {editormd}                  this(editormd instance object.)
903          */
904         
905         set : function (key, value) {
906             
907             if (typeof value !== "undefined" && typeof value === "function")
908             {
909                 value = $.proxy(value, this);
910             }
911             
912             this[key] = value;
913
914             return this;
915         },
916         
917         /**
918          * 重新配置
919          * Resetting editor options
920          * 
921          * @param   {String|Object}   key       option key
922          * @param   {String|Object}   value     option value
923          * @returns {editormd}                  this(editormd instance object.)
924          */
925         
926         config : function(key, value) {
927             var settings = this.settings;
928             
929             if (typeof key === "object")
930             {
931                 settings = $.extend(true, settings, key);
932             }
933             
934             if (typeof key === "string")
935             {
936                 settings[key] = value;
937             }
938             
939             this.settings = settings;
940             this.recreate();
941             
942             return this;
943         },
944         
945         /**
946          * 注册事件处理方法
947          * Bind editor event handle
948          * 
949          * @param   {String}     eventType      event type
950          * @param   {Function}   callback       回调函数
951          * @returns {editormd}                  this(editormd instance object.)
952          */
953         
954         on : function(eventType, callback) {
955             var settings = this.settings;
956             
957             if (typeof settings["on" + eventType] !== "undefined") 
958             {                
959                 settings["on" + eventType] = $.proxy(callback, this);      
960             }
961
962             return this;
963         },
964         
965         /**
966          * 解除事件处理方法
967          * Unbind editor event handle
968          * 
969          * @param   {String}   eventType          event type
970          * @returns {editormd}                    this(editormd instance object.)
971          */
972         
973         off : function(eventType) {
974             var settings = this.settings;
975             
976             if (typeof settings["on" + eventType] !== "undefined") 
977             {
978                 settings["on" + eventType] = function(){};
979             }
980             
981             return this;
982         },
983         
984         /**
985          * 显示工具栏
986          * Display toolbar
987          * 
988          * @param   {Function} [callback=function(){}] 回调函数
989          * @returns {editormd}  返回editormd的实例对象
990          */
991         
992         showToolbar : function(callback) {
993             var settings = this.settings;
994             
995             if(settings.readOnly) {
996                 return this;
997             }
998             
999             if (settings.toolbar && (this.toolbar.length < 1 || this.toolbar.find("." + this.classPrefix + "menu").html() === "") )
1000             {
1001                 this.setToolbar();
1002             }
1003             
1004             settings.toolbar = true; 
1005             
1006             this.toolbar.show();
1007             this.resize();
1008             
1009             $.proxy(callback || function(){}, this)();
1010
1011             return this;
1012         },
1013         
1014         /**
1015          * 隐藏工具栏
1016          * Hide toolbar
1017          * 
1018          * @param   {Function} [callback=function(){}] 回调函数
1019          * @returns {editormd}                         this(editormd instance object.)
1020          */
1021         
1022         hideToolbar : function(callback) { 
1023             var settings = this.settings;
1024             
1025             settings.toolbar = false;  
1026             this.toolbar.hide();
1027             this.resize();
1028             
1029             $.proxy(callback || function(){}, this)();
1030
1031             return this;
1032         },
1033         
1034         /**
1035          * 页面滚动时工具栏的固定定位
1036          * Set toolbar in window scroll auto fixed position
1037          * 
1038          * @returns {editormd}  返回editormd的实例对象
1039          */
1040         
1041         setToolbarAutoFixed : function(fixed) {
1042             
1043             var state    = this.state;
1044             var editor   = this.editor;
1045             var toolbar  = this.toolbar;
1046             var settings = this.settings;
1047             
1048             if (typeof fixed !== "undefined")
1049             {
1050                 settings.toolbarAutoFixed = fixed;
1051             }
1052             
1053             var autoFixedHandle = function(){
1054                 var $window = $(window);
1055                 var top     = $window.scrollTop();
1056                 
1057                 if (!settings.toolbarAutoFixed)
1058                 {
1059                     return false;
1060                 }
1061
1062                 if (top - editor.offset().top > 10 && top < editor.height())
1063                 {
1064                     toolbar.css({
1065                         position : "fixed",
1066                         width    : editor.width() + "px",
1067                         left     : ($window.width() - editor.width()) / 2 + "px"
1068                     });
1069                 }
1070                 else
1071                 {
1072                     toolbar.css({
1073                         position : "absolute",
1074                         width    : "100%",
1075                         left     : 0
1076                     });
1077                 }
1078             };
1079             
1080             if (!state.fullscreen && !state.preview && settings.toolbar && settings.toolbarAutoFixed)
1081             {
1082                 $(window).bind("scroll", autoFixedHandle);
1083             }
1084
1085             return this;
1086         },
1087         
1088         /**
1089          * 配置和初始化工具栏
1090          * Set toolbar and Initialization
1091          * 
1092          * @returns {editormd}  返回editormd的实例对象
1093          */
1094         
1095         setToolbar : function() {
1096             var settings    = this.settings;  
1097             
1098             if(settings.readOnly) {
1099                 return this;
1100             }
1101             
1102             var editor      = this.editor;
1103             var preview     = this.preview;
1104             var classPrefix = this.classPrefix;
1105             
1106             var toolbar     = this.toolbar = editor.children("." + classPrefix + "toolbar");
1107             
1108             if (settings.toolbar && toolbar.length < 1)
1109             {            
1110                 var toolbarHTML = "<div class=\"" + classPrefix + "toolbar\"><div class=\"" + classPrefix + "toolbar-container\"><ul class=\"" + classPrefix + "menu\"></ul></div></div>";
1111                 
1112                 editor.append(toolbarHTML);
1113                 toolbar = this.toolbar = editor.children("." + classPrefix + "toolbar");
1114             }
1115             
1116             if (!settings.toolbar) 
1117             {
1118                 toolbar.hide();
1119                 
1120                 return this;
1121             }
1122             
1123             toolbar.show();
1124             
1125             var icons       = (typeof settings.toolbarIcons === "function") ? settings.toolbarIcons() 
1126                             : ((typeof settings.toolbarIcons === "string")  ? editormd.toolbarModes[settings.toolbarIcons] : settings.toolbarIcons);
1127             
1128             var toolbarMenu = toolbar.find("." + this.classPrefix + "menu"), menu = "";
1129             var pullRight   = false;
1130             
1131             for (var i = 0, len = icons.length; i < len; i++)
1132             {
1133                 var name = icons[i];
1134
1135                 if (name === "||") 
1136                 { 
1137                     pullRight = true;
1138                 } 
1139                 else if (name === "|")
1140                 {
1141                     menu += "<li class=\"divider\" unselectable=\"on\">|</li>";
1142                 }
1143                 else
1144                 {
1145                     var isHeader = (/h(\d)/.test(name));
1146                     var index    = name;
1147                     
1148                     if (name === "watch" && !settings.watch) {
1149                         index = "unwatch";
1150                     }
1151                     
1152                     var title     = settings.lang.toolbar[index];
1153                     var iconTexts = settings.toolbarIconTexts[index];
1154                     var iconClass = settings.toolbarIconsClass[index];
1155                     
1156                     title     = (typeof title     === "undefined") ? "" : title;
1157                     iconTexts = (typeof iconTexts === "undefined") ? "" : iconTexts;
1158                     iconClass = (typeof iconClass === "undefined") ? "" : iconClass;
1159
1160                     var menuItem = pullRight ? "<li class=\"pull-right\">" : "<li>";
1161                     
1162                     if (typeof settings.toolbarCustomIcons[name] !== "undefined" && typeof settings.toolbarCustomIcons[name] !== "function")
1163                     {
1164                         menuItem += settings.toolbarCustomIcons[name];
1165                     }
1166                     else 
1167                     {
1168                         menuItem += "<a href=\"javascript:;\" title=\"" + title + "\" unselectable=\"on\">";
1169                         menuItem += "<i class=\"fa " + iconClass + "\" name=\""+name+"\" unselectable=\"on\">"+((isHeader) ? name.toUpperCase() : ( (iconClass === "") ? iconTexts : "") ) + "</i>";
1170                         menuItem += "</a>";
1171                     }
1172
1173                     menuItem += "</li>";
1174
1175                     menu = pullRight ? menuItem + menu : menu + menuItem;
1176                 }
1177             }
1178
1179             toolbarMenu.html(menu);
1180             
1181             toolbarMenu.find("[title=\"Lowercase\"]").attr("title", settings.lang.toolbar.lowercase);
1182             toolbarMenu.find("[title=\"ucwords\"]").attr("title", settings.lang.toolbar.ucwords);
1183             
1184             this.setToolbarHandler();
1185             this.setToolbarAutoFixed();
1186
1187             return this;
1188         },
1189         
1190         /**
1191          * 工具栏图标事件处理对象序列
1192          * Get toolbar icons event handlers
1193          * 
1194          * @param   {Object}   cm    CodeMirror的实例对象
1195          * @param   {String}   name  要获取的事件处理器名称
1196          * @returns {Object}         返回处理对象序列
1197          */
1198             
1199         dialogLockScreen : function() {
1200             $.proxy(editormd.dialogLockScreen, this)();
1201             
1202             return this;
1203         },
1204
1205         dialogShowMask : function(dialog) {
1206             $.proxy(editormd.dialogShowMask, this)(dialog);
1207             
1208             return this;
1209         },
1210         
1211         getToolbarHandles : function(name) {  
1212             var toolbarHandlers = this.toolbarHandlers = editormd.toolbarHandlers;
1213             
1214             return (name && typeof toolbarIconHandlers[name] !== "undefined") ? toolbarHandlers[name] : toolbarHandlers;
1215         },
1216         
1217         /**
1218          * 工具栏图标事件处理器
1219          * Bind toolbar icons event handle
1220          * 
1221          * @returns {editormd}  返回editormd的实例对象
1222          */
1223         
1224         setToolbarHandler : function() {
1225             var _this               = this;
1226             var settings            = this.settings;
1227             
1228             if (!settings.toolbar || settings.readOnly) {
1229                 return this;
1230             }
1231             
1232             var toolbar             = this.toolbar;
1233             var cm                  = this.cm;
1234             var classPrefix         = this.classPrefix;           
1235             var toolbarIcons        = this.toolbarIcons = toolbar.find("." + classPrefix + "menu > li > a");  
1236             var toolbarIconHandlers = this.getToolbarHandles();  
1237                 
1238             toolbarIcons.bind(editormd.mouseOrTouch("click", "touchend"), function(event) {
1239
1240                 var icon                = $(this).children(".fa");
1241                 var name                = icon.attr("name");
1242                 var cursor              = cm.getCursor();
1243                 var selection           = cm.getSelection();
1244
1245                 if (name === "") {
1246                     return ;
1247                 }
1248                 
1249                 _this.activeIcon = icon;
1250
1251                 if (typeof toolbarIconHandlers[name] !== "undefined") 
1252                 {
1253                     $.proxy(toolbarIconHandlers[name], _this)(cm);
1254                 }
1255                 else 
1256                 {
1257                     if (typeof settings.toolbarHandlers[name] !== "undefined") 
1258                     {
1259                         $.proxy(settings.toolbarHandlers[name], _this)(cm, icon, cursor, selection);
1260                     }
1261                 }
1262                 
1263                 if (name !== "link" && name !== "reference-link" && name !== "image" && name !== "code-block" && 
1264                     name !== "preformatted-text" && name !== "watch" && name !== "preview" && name !== "search" && name !== "fullscreen" && name !== "info") 
1265                 {
1266                     cm.focus();
1267                 }
1268
1269                 return false;
1270
1271             });
1272
1273             return this;
1274         },
1275         
1276         /**
1277          * 动态创建对话框
1278          * Creating custom dialogs
1279          * 
1280          * @param   {Object} options  配置项键值对 Key/Value
1281          * @returns {dialog}          返回创建的dialog的jQuery实例对象
1282          */
1283         
1284         createDialog : function(options) {            
1285             return $.proxy(editormd.createDialog, this)(options);
1286         },
1287         
1288         /**
1289          * 创建关于Editor.md的对话框
1290          * Create about Editor.md dialog
1291          * 
1292          * @returns {editormd}  返回editormd的实例对象
1293          */
1294         
1295         createInfoDialog : function() {
1296             var _this        = this;
1297             var editor       = this.editor;
1298             var classPrefix  = this.classPrefix;  
1299             
1300             var infoDialogHTML = [
1301                 "<div class=\"" + classPrefix + "dialog " + classPrefix + "dialog-info\" style=\"\">",
1302                 "<div class=\"" + classPrefix + "dialog-container\">",
1303                 "<h1><i class=\"editormd-logo editormd-logo-lg editormd-logo-color\"></i> " + editormd.title + "<small>v" + editormd.version + "</small></h1>",
1304                 "<p>" + this.lang.description + "</p>",
1305                 "<p style=\"margin: 10px 0 20px 0;\"><a href=\"" + editormd.homePage + "\" target=\"_blank\">" + editormd.homePage + " <i class=\"fa fa-external-link\"></i></a></p>",
1306                 "<p style=\"font-size: 0.85em;\">Copyright &copy; 2015 <a href=\"https://github.com/pandao\" target=\"_blank\" class=\"hover-link\">Pandao</a>, The <a href=\"https://github.com/pandao/editor.md/blob/master/LICENSE\" target=\"_blank\" class=\"hover-link\">MIT</a> License.</p>",
1307                 "</div>",
1308                 "<a href=\"javascript:;\" class=\"fa fa-close " + classPrefix + "dialog-close\"></a>",
1309                 "</div>"
1310             ].join("\n");
1311
1312             editor.append(infoDialogHTML);
1313             
1314             var infoDialog  = this.infoDialog = editor.children("." + classPrefix + "dialog-info");
1315
1316             infoDialog.find("." + classPrefix + "dialog-close").bind(editormd.mouseOrTouch("click", "touchend"), function() {
1317                 _this.hideInfoDialog();
1318             });
1319             
1320             infoDialog.css("border", (editormd.isIE8) ? "1px solid #ddd" : "").css("z-index", editormd.dialogZindex).show();
1321             
1322             this.infoDialogPosition();
1323
1324             return this;
1325         },
1326         
1327         /**
1328          * 关于Editor.md对话居中定位
1329          * Editor.md dialog position handle
1330          * 
1331          * @returns {editormd}  返回editormd的实例对象
1332          */
1333         
1334         infoDialogPosition : function() {
1335             var infoDialog = this.infoDialog;
1336             
1337             var _infoDialogPosition = function() {
1338                 infoDialog.css({
1339                     top  : ($(window).height() - infoDialog.height()) / 2 + "px",
1340                     left : ($(window).width()  - infoDialog.width()) / 2  + "px"
1341                 });
1342             };
1343
1344             _infoDialogPosition();
1345
1346             $(window).resize(_infoDialogPosition);
1347             
1348             return this;
1349         },
1350         
1351         /**
1352          * 显示关于Editor.md
1353          * Display about Editor.md dialog
1354          * 
1355          * @returns {editormd}  返回editormd的实例对象
1356          */
1357         
1358         showInfoDialog : function() {
1359
1360             $("html,body").css("overflow-x", "hidden");
1361             
1362             var _this       = this;
1363             var editor      = this.editor;
1364             var settings    = this.settings;         
1365             var infoDialog  = this.infoDialog = editor.children("." + this.classPrefix + "dialog-info");
1366             
1367             if (infoDialog.length < 1)
1368             {
1369                 this.createInfoDialog();
1370             }
1371             
1372             this.lockScreen(true);
1373             
1374             this.mask.css({
1375                         opacity         : settings.dialogMaskOpacity,
1376                         backgroundColor : settings.dialogMaskBgColor
1377                     }).show();
1378
1379             infoDialog.css("z-index", editormd.dialogZindex).show();
1380
1381             this.infoDialogPosition();
1382
1383             return this;
1384         },
1385         
1386         /**
1387          * 隐藏关于Editor.md
1388          * Hide about Editor.md dialog
1389          * 
1390          * @returns {editormd}  返回editormd的实例对象
1391          */
1392         
1393         hideInfoDialog : function() {            
1394             $("html,body").css("overflow-x", "");
1395             this.infoDialog.hide();
1396             this.mask.hide();
1397             this.lockScreen(false);
1398
1399             return this;
1400         },
1401         
1402         /**
1403          * 锁屏
1404          * lock screen
1405          * 
1406          * @param   {Boolean}    lock    Boolean 布尔值,是否锁屏
1407          * @returns {editormd}           返回editormd的实例对象
1408          */
1409         
1410         lockScreen : function(lock) {
1411             editormd.lockScreen(lock);
1412             this.resize();
1413
1414             return this;
1415         },
1416         
1417         /**
1418          * 编辑器界面重建,用于动态语言包或模块加载等
1419          * Recreate editor
1420          * 
1421          * @returns {editormd}  返回editormd的实例对象
1422          */
1423         
1424         recreate : function() {
1425             var _this            = this;
1426             var editor           = this.editor;
1427             var settings         = this.settings;
1428             
1429             this.codeMirror.remove();
1430             
1431             this.setCodeMirror();
1432
1433             if (!settings.readOnly) 
1434             {
1435                 if (editor.find(".editormd-dialog").length > 0) {
1436                     editor.find(".editormd-dialog").remove();
1437                 }
1438                 
1439                 if (settings.toolbar) 
1440                 {  
1441                     this.getToolbarHandles();                  
1442                     this.setToolbar();
1443                 }
1444             }
1445             
1446             this.loadedDisplay(true);
1447
1448             return this;
1449         },
1450         
1451         /**
1452          * 高亮预览HTML的pre代码部分
1453          * highlight of preview codes
1454          * 
1455          * @returns {editormd}             返回editormd的实例对象
1456          */
1457         
1458         previewCodeHighlight : function() {    
1459             var settings         = this.settings;
1460             var previewContainer = this.previewContainer;
1461             
1462             if (settings.previewCodeHighlight) 
1463             {
1464                 previewContainer.find("pre").addClass("prettyprint linenums");
1465                 
1466                 if (typeof prettyPrint !== "undefined")
1467                 {                    
1468                     prettyPrint();
1469                 }
1470             }
1471
1472             return this;
1473         },
1474         
1475         /**
1476          * 解析TeX(KaTeX)科学公式
1477          * TeX(KaTeX) Renderer
1478          * 
1479          * @returns {editormd}             返回editormd的实例对象
1480          */
1481         
1482         katexRender : function() {
1483             
1484             if (timer === null)
1485             {
1486                 return this;
1487             }
1488             
1489             this.previewContainer.find("." + editormd.classNames.tex).each(function(){
1490                 var tex  = $(this);
1491                 editormd.$katex.render(tex.text(), tex[0]);
1492                 
1493                 tex.find(".katex").css("font-size", "1.6em");
1494             });   
1495
1496             return this;
1497         },
1498         
1499         /**
1500          * 解析和渲染流程图及时序图
1501          * FlowChart and SequenceDiagram Renderer
1502          * 
1503          * @returns {editormd}             返回editormd的实例对象
1504          */
1505         
1506         flowChartAndSequenceDiagramRender : function() {
1507             var $this            = this;
1508             var settings         = this.settings;
1509             var previewContainer = this.previewContainer;
1510             
1511             if (editormd.isIE8) {
1512                 return this;
1513             }
1514
1515             if (settings.flowChart) {
1516                 if (flowchartTimer === null) {
1517                     return this;
1518                 }
1519                 
1520                 previewContainer.find(".flowchart").flowChart(); 
1521             }
1522
1523             if (settings.sequenceDiagram) {
1524                 previewContainer.find(".sequence-diagram").sequenceDiagram({theme: "simple"});
1525             }
1526                     
1527             var preview    = $this.preview;
1528             var codeMirror = $this.codeMirror;
1529             var codeView   = codeMirror.find(".CodeMirror-scroll");
1530
1531             var height    = codeView.height();
1532             var scrollTop = codeView.scrollTop();                    
1533             var percent   = (scrollTop / codeView[0].scrollHeight);
1534             var tocHeight = 0;
1535
1536             preview.find(".markdown-toc-list").each(function(){
1537                 tocHeight += $(this).height();
1538             });
1539
1540             var tocMenuHeight = preview.find(".editormd-toc-menu").height(); 
1541             tocMenuHeight = (!tocMenuHeight) ? 0 : tocMenuHeight;
1542
1543             if (scrollTop === 0) 
1544             {
1545                 preview.scrollTop(0);
1546             } 
1547             else if (scrollTop + height >= codeView[0].scrollHeight - 16)
1548             { 
1549                 preview.scrollTop(preview[0].scrollHeight);                        
1550             } 
1551             else
1552             {                  
1553                 preview.scrollTop((preview[0].scrollHeight + tocHeight + tocMenuHeight) * percent);
1554             }
1555
1556             return this;
1557         },
1558         
1559         /**
1560          * 注册键盘快捷键处理
1561          * Register CodeMirror keyMaps (keyboard shortcuts).
1562          * 
1563          * @param   {Object}    keyMap      KeyMap key/value {"(Ctrl/Shift/Alt)-Key" : function(){}}
1564          * @returns {editormd}              return this
1565          */
1566         
1567         registerKeyMaps : function(keyMap) {
1568             
1569             var _this           = this;
1570             var cm              = this.cm;
1571             var settings        = this.settings;
1572             var toolbarHandlers = editormd.toolbarHandlers;
1573             var disabledKeyMaps = settings.disabledKeyMaps;
1574             
1575             keyMap              = keyMap || null;
1576             
1577             if (keyMap)
1578             {
1579                 for (var i in keyMap)
1580                 {
1581                     if ($.inArray(i, disabledKeyMaps) < 0)
1582                     {
1583                         var map = {};
1584                         map[i]  = keyMap[i];
1585
1586                         cm.addKeyMap(keyMap);
1587                     }
1588                 }
1589             }
1590             else
1591             {
1592                 for (var k in editormd.keyMaps)
1593                 {
1594                     var _keyMap = editormd.keyMaps[k];
1595                     var handle = (typeof _keyMap === "string") ? $.proxy(toolbarHandlers[_keyMap], _this) : $.proxy(_keyMap, _this);
1596                     
1597                     if ($.inArray(k, ["F9", "F10", "F11"]) < 0 && $.inArray(k, disabledKeyMaps) < 0)
1598                     {
1599                         var _map = {};
1600                         _map[k] = handle;
1601
1602                         cm.addKeyMap(_map);
1603                     }
1604                 }
1605                 
1606                 $(window).keydown(function(event) {
1607                     
1608                     var keymaps = {
1609                         "120" : "F9",
1610                         "121" : "F10",
1611                         "122" : "F11"
1612                     };
1613                     
1614                     if ( $.inArray(keymaps[event.keyCode], disabledKeyMaps) < 0 )
1615                     {
1616                         switch (event.keyCode)
1617                         {
1618                             case 120:
1619                                     $.proxy(toolbarHandlers["watch"], _this)();
1620                                     return false;
1621                                 break;
1622                                 
1623                             case 121:
1624                                     $.proxy(toolbarHandlers["preview"], _this)();
1625                                     return false;
1626                                 break;
1627                                 
1628                             case 122:
1629                                     $.proxy(toolbarHandlers["fullscreen"], _this)();                        
1630                                     return false;
1631                                 break;
1632                                 
1633                             default:
1634                                 break;
1635                         }
1636                     }
1637                 });
1638             }
1639
1640             return this;
1641         },
1642         
1643         /**
1644          * 绑定同步滚动
1645          * 
1646          * @returns {editormd} return this
1647          */
1648         
1649         bindScrollEvent : function() {
1650             
1651             var _this            = this;
1652             var preview          = this.preview;
1653             var settings         = this.settings;
1654             var codeMirror       = this.codeMirror;
1655             var mouseOrTouch     = editormd.mouseOrTouch;
1656             
1657             if (!settings.syncScrolling) {
1658                 return this;
1659             }
1660                 
1661             var cmBindScroll = function() {    
1662                 codeMirror.find(".CodeMirror-scroll").bind(mouseOrTouch("scroll", "touchmove"), function(event) {
1663                     var height    = $(this).height();
1664                     var scrollTop = $(this).scrollTop();                    
1665                     var percent   = (scrollTop / $(this)[0].scrollHeight);
1666                     
1667                     var tocHeight = 0;
1668                     
1669                     preview.find(".markdown-toc-list").each(function(){
1670                         tocHeight += $(this).height();
1671                     });
1672                     
1673                     var tocMenuHeight = preview.find(".editormd-toc-menu").height();
1674                     tocMenuHeight = (!tocMenuHeight) ? 0 : tocMenuHeight;
1675
1676                     if (scrollTop === 0) 
1677                     {
1678                         preview.scrollTop(0);
1679                     } 
1680                     else if (scrollTop + height >= $(this)[0].scrollHeight - 16)
1681                     { 
1682                         preview.scrollTop(preview[0].scrollHeight);                        
1683                     } 
1684                     else
1685                     {
1686                         preview.scrollTop((preview[0].scrollHeight  + tocHeight + tocMenuHeight) * percent);
1687                     }
1688                     
1689                     $.proxy(settings.onscroll, _this)(event);
1690                 });
1691             };
1692
1693             var cmUnbindScroll = function() {
1694                 codeMirror.find(".CodeMirror-scroll").unbind(mouseOrTouch("scroll", "touchmove"));
1695             };
1696
1697             var previewBindScroll = function() {
1698                 
1699                 preview.bind(mouseOrTouch("scroll", "touchmove"), function(event) {
1700                     var height    = $(this).height();
1701                     var scrollTop = $(this).scrollTop();         
1702                     var percent   = (scrollTop / $(this)[0].scrollHeight);
1703                     var codeView  = codeMirror.find(".CodeMirror-scroll");
1704
1705                     if(scrollTop === 0) 
1706                     {
1707                         codeView.scrollTop(0);
1708                     }
1709                     else if (scrollTop + height >= $(this)[0].scrollHeight)
1710                     {
1711                         codeView.scrollTop(codeView[0].scrollHeight);                        
1712                     }
1713                     else 
1714                     {
1715                         codeView.scrollTop(codeView[0].scrollHeight * percent);
1716                     }
1717                     
1718                     $.proxy(settings.onpreviewscroll, _this)(event);
1719                 });
1720
1721             };
1722
1723             var previewUnbindScroll = function() {
1724                 preview.unbind(mouseOrTouch("scroll", "touchmove"));
1725             }; 
1726
1727             codeMirror.bind({
1728                 mouseover  : cmBindScroll,
1729                 mouseout   : cmUnbindScroll,
1730                 touchstart : cmBindScroll,
1731                 touchend   : cmUnbindScroll
1732             });
1733             
1734             if (settings.syncScrolling === "single") {
1735                 return this;
1736             }
1737             
1738             preview.bind({
1739                 mouseover  : previewBindScroll,
1740                 mouseout   : previewUnbindScroll,
1741                 touchstart : previewBindScroll,
1742                 touchend   : previewUnbindScroll
1743             });
1744
1745             return this;
1746         },
1747         
1748         bindChangeEvent : function() {
1749             
1750             var _this            = this;
1751             var cm               = this.cm;
1752             var settings         = this.settings;
1753             
1754             if (!settings.syncScrolling) {
1755                 return this;
1756             }
1757             
1758             cm.on("change", function(_cm, changeObj) {
1759                 
1760                 if (settings.watch)
1761                 {
1762                     _this.previewContainer.css("padding", settings.autoHeight ? "20px 20px 50px 40px" : "20px");
1763                 }
1764                 
1765                 timer = setTimeout(function() {
1766                     clearTimeout(timer);
1767                     _this.save();
1768                     timer = null;
1769                 }, settings.delay);
1770             });
1771
1772             return this;
1773         },
1774         
1775         /**
1776          * 加载队列完成之后的显示处理
1777          * Display handle of the module queues loaded after.
1778          * 
1779          * @param   {Boolean}   recreate   是否为重建编辑器
1780          * @returns {editormd}             返回editormd的实例对象
1781          */
1782         
1783         loadedDisplay : function(recreate) {
1784             
1785             recreate             = recreate || false;
1786             
1787             var _this            = this;
1788             var editor           = this.editor;
1789             var preview          = this.preview;
1790             var settings         = this.settings;
1791             
1792             this.containerMask.hide();
1793             
1794             this.save();
1795             
1796             if (settings.watch) {
1797                 preview.show();
1798             }
1799             
1800             editor.data("oldWidth", editor.width()).data("oldHeight", editor.height()); // 为了兼容Zepto
1801             
1802             this.resize();
1803             this.registerKeyMaps();
1804             
1805             $(window).resize(function(){
1806                 _this.resize();
1807             });
1808             
1809             this.bindScrollEvent().bindChangeEvent();
1810             
1811             if (!recreate)
1812             {
1813                 $.proxy(settings.onload, this)();
1814             }
1815             
1816             this.state.loaded = true;
1817
1818             return this;
1819         },
1820         
1821         /**
1822          * 设置编辑器的宽度
1823          * Set editor width
1824          * 
1825          * @param   {Number|String} width  编辑器宽度值
1826          * @returns {editormd}             返回editormd的实例对象
1827          */
1828         
1829         width : function(width) {
1830                 
1831             this.editor.css("width", (typeof width === "number") ? width  + "px" : width);            
1832             this.resize();
1833             
1834             return this;
1835         },
1836         
1837         /**
1838          * 设置编辑器的高度
1839          * Set editor height
1840          * 
1841          * @param   {Number|String} height  编辑器高度值
1842          * @returns {editormd}              返回editormd的实例对象
1843          */
1844         
1845         height : function(height) {
1846                 
1847             this.editor.css("height", (typeof height === "number")  ? height  + "px" : height);            
1848             this.resize();
1849             
1850             return this;
1851         },
1852         
1853         /**
1854          * 调整编辑器的尺寸和布局
1855          * Resize editor layout
1856          * 
1857          * @param   {Number|String} [width=null]  编辑器宽度值
1858          * @param   {Number|String} [height=null] 编辑器高度值
1859          * @returns {editormd}                    返回editormd的实例对象
1860          */
1861         
1862         resize : function(width, height) {
1863             
1864             width  = width  || null;
1865             height = height || null;
1866             
1867             var state      = this.state;
1868             var editor     = this.editor;
1869             var preview    = this.preview;
1870             var toolbar    = this.toolbar;
1871             var settings   = this.settings;
1872             var codeMirror = this.codeMirror;
1873             
1874             if (width)
1875             {
1876                 editor.css("width", (typeof width  === "number") ? width  + "px" : width);
1877             }
1878             
1879             if (settings.autoHeight && !state.fullscreen && !state.preview)
1880             {
1881                 editor.css("height", "auto");
1882                 codeMirror.css("height", "auto");
1883             } 
1884             else 
1885             {
1886                 if (height) 
1887                 {
1888                     editor.css("height", (typeof height === "number") ? height + "px" : height);
1889                 }
1890                 
1891                 if (state.fullscreen)
1892                 {
1893                     editor.height($(window).height());
1894                 }
1895
1896                 if (settings.toolbar && !settings.readOnly) 
1897                 {
1898                     codeMirror.css("margin-top", toolbar.height() + 1).height(editor.height() - toolbar.height());
1899                 } 
1900                 else
1901                 {
1902                     codeMirror.css("margin-top", 0).height(editor.height());
1903                 }
1904             }
1905             
1906             if(settings.watch) 
1907             {
1908                 codeMirror.width(editor.width() / 2);
1909                 preview.width((!state.preview) ? editor.width() / 2 : editor.width());
1910                 
1911                 this.previewContainer.css("padding", settings.autoHeight ? "20px 20px 50px 40px" : "20px");
1912                 
1913                 if (settings.toolbar && !settings.readOnly) 
1914                 {
1915                     preview.css("top", toolbar.height() + 1);
1916                 } 
1917                 else 
1918                 {
1919                     preview.css("top", 0);
1920                 }
1921                 
1922                 if (settings.autoHeight && !state.fullscreen && !state.preview)
1923                 {
1924                     preview.height("");
1925                 }
1926                 else
1927                 {                
1928                     var previewHeight = (settings.toolbar && !settings.readOnly) ? editor.height() - toolbar.height() : editor.height();
1929                     
1930                     preview.height(previewHeight);
1931                 }
1932             } 
1933             else 
1934             {
1935                 codeMirror.width(editor.width());
1936                 preview.hide();
1937             }
1938             
1939             if (state.loaded) 
1940             {
1941                 $.proxy(settings.onresize, this)();
1942             }
1943
1944             return this;
1945         },
1946         
1947         /**
1948          * 解析和保存Markdown代码
1949          * Parse & Saving Markdown source code
1950          * 
1951          * @returns {editormd}     返回editormd的实例对象
1952          */
1953         
1954         save : function() {
1955             
1956             if (timer === null)
1957             {
1958                 return this;
1959             }
1960             
1961             var _this            = this;
1962             var state            = this.state;
1963             var settings         = this.settings;
1964             var cm               = this.cm;            
1965             var cmValue          = cm.getValue();
1966             var previewContainer = this.previewContainer;
1967
1968             if (settings.mode !== "gfm" && settings.mode !== "markdown") 
1969             {
1970                 this.markdownTextarea.val(cmValue);
1971                 
1972                 return this;
1973             }
1974             
1975             var marked          = editormd.$marked;
1976             var markdownToC     = this.markdownToC = [];            
1977             var rendererOptions = this.markedRendererOptions = {  
1978                 toc                  : settings.toc,
1979                 tocm                 : settings.tocm,
1980                 tocStartLevel        : settings.tocStartLevel,
1981                 pageBreak            : settings.pageBreak,
1982                 taskList             : settings.taskList,
1983                 emoji                : settings.emoji,
1984                 tex                  : settings.tex,
1985                 atLink               : settings.atLink,           // for @link
1986                 emailLink            : settings.emailLink,        // for mail address auto link
1987                 flowChart            : settings.flowChart,
1988                 sequenceDiagram      : settings.sequenceDiagram,
1989                 previewCodeHighlight : settings.previewCodeHighlight,
1990             };
1991             
1992             var markedOptions = this.markedOptions = {
1993                 renderer    : editormd.markedRenderer(markdownToC, rendererOptions),
1994                 gfm         : true,
1995                 tables      : true,
1996                 breaks      : true,
1997                 pedantic    : false,
1998                 sanitize    : (settings.htmlDecode) ? false : true,  // 关闭忽略HTML标签,即开启识别HTML标签,默认为false
1999                 smartLists  : true,
2000                 smartypants : true
2001             };
2002             
2003             marked.setOptions(markedOptions);
2004                     
2005             var newMarkdownDoc = editormd.$marked(cmValue, markedOptions);
2006             
2007             //console.info("cmValue", cmValue, newMarkdownDoc);
2008             
2009             newMarkdownDoc = editormd.filterHTMLTags(newMarkdownDoc, settings.htmlDecode);
2010             
2011             //console.error("cmValue", cmValue, newMarkdownDoc);
2012             
2013             this.markdownTextarea.text(cmValue);
2014             
2015             cm.save();
2016             
2017             if (settings.saveHTMLToTextarea) 
2018             {
2019                 this.htmlTextarea.text(newMarkdownDoc);
2020             }
2021             
2022             if(settings.watch || (!settings.watch && state.preview))
2023             {
2024                 previewContainer.html(newMarkdownDoc);
2025
2026                 this.previewCodeHighlight();
2027                 
2028                 if (settings.toc) 
2029                 {
2030                     var tocContainer = (settings.tocContainer === "") ? previewContainer : $(settings.tocContainer);
2031                     var tocMenu      = tocContainer.find("." + this.classPrefix + "toc-menu");
2032                     
2033                     tocContainer.attr("previewContainer", (settings.tocContainer === "") ? "true" : "false");
2034                     
2035                     if (settings.tocContainer !== "" && tocMenu.length > 0)
2036                     {
2037                         tocMenu.remove();
2038                     }
2039                     
2040                     editormd.markdownToCRenderer(markdownToC, tocContainer, settings.tocDropdown, settings.tocStartLevel);
2041             
2042                     if (settings.tocDropdown || tocContainer.find("." + this.classPrefix + "toc-menu").length > 0)
2043                     {
2044                         editormd.tocDropdownMenu(tocContainer, (settings.tocTitle !== "") ? settings.tocTitle : this.lang.tocTitle);
2045                     }
2046             
2047                     if (settings.tocContainer !== "")
2048                     {
2049                         previewContainer.find(".markdown-toc").css("border", "none");
2050                     }
2051                 }
2052                 
2053                 if (settings.tex)
2054                 {
2055                     if (!editormd.kaTeXLoaded && settings.autoLoadModules) 
2056                     {
2057                         editormd.loadKaTeX(function() {
2058                             editormd.$katex = katex;
2059                             editormd.kaTeXLoaded = true;
2060                             _this.katexRender();
2061                         });
2062                     } 
2063                     else 
2064                     {
2065                         editormd.$katex = katex;
2066                         this.katexRender();
2067                     }
2068                 }                
2069                 
2070                 if (settings.flowChart || settings.sequenceDiagram)
2071                 {
2072                     flowchartTimer = setTimeout(function(){
2073                         clearTimeout(flowchartTimer);
2074                         _this.flowChartAndSequenceDiagramRender();
2075                         flowchartTimer = null;
2076                     }, 10);
2077                 }
2078
2079                 if (state.loaded) 
2080                 {
2081                     $.proxy(settings.onchange, this)();
2082                 }
2083             }
2084
2085             return this;
2086         },
2087         
2088         /**
2089          * 聚焦光标位置
2090          * Focusing the cursor position
2091          * 
2092          * @returns {editormd}         返回editormd的实例对象
2093          */
2094         
2095         focus : function() {
2096             this.cm.focus();
2097
2098             return this;
2099         },
2100         
2101         /**
2102          * 设置光标的位置
2103          * Set cursor position
2104          * 
2105          * @param   {Object}    cursor 要设置的光标位置键值对象,例:{line:1, ch:0}
2106          * @returns {editormd}         返回editormd的实例对象
2107          */
2108         
2109         setCursor : function(cursor) {
2110             this.cm.setCursor(cursor);
2111
2112             return this;
2113         },
2114         
2115         /**
2116          * 获取当前光标的位置
2117          * Get the current position of the cursor
2118          * 
2119          * @returns {Cursor}         返回一个光标Cursor对象
2120          */
2121         
2122         getCursor : function() {
2123             return this.cm.getCursor();
2124         },
2125         
2126         /**
2127          * 设置光标选中的范围
2128          * Set cursor selected ranges
2129          * 
2130          * @param   {Object}    from   开始位置的光标键值对象,例:{line:1, ch:0}
2131          * @param   {Object}    to     结束位置的光标键值对象,例:{line:1, ch:0}
2132          * @returns {editormd}         返回editormd的实例对象
2133          */
2134         
2135         setSelection : function(from, to) {
2136         
2137             this.cm.setSelection(from, to);
2138         
2139             return this;
2140         },
2141         
2142         /**
2143          * 获取光标选中的文本
2144          * Get the texts from cursor selected
2145          * 
2146          * @returns {String}         返回选中文本的字符串形式
2147          */
2148         
2149         getSelection : function() {
2150             return this.cm.getSelection();
2151         },
2152         
2153         /**
2154          * 设置光标选中的文本范围
2155          * Set the cursor selection ranges
2156          * 
2157          * @param   {Array}    ranges  cursor selection ranges array
2158          * @returns {Array}            return this
2159          */
2160         
2161         setSelections : function(ranges) {
2162             this.cm.setSelections(ranges);
2163             
2164             return this;
2165         },
2166         
2167         /**
2168          * 获取光标选中的文本范围
2169          * Get the cursor selection ranges
2170          * 
2171          * @returns {Array}         return selection ranges array
2172          */
2173         
2174         getSelections : function() {
2175             return this.cm.getSelections();
2176         },
2177         
2178         /**
2179          * 替换当前光标选中的文本或在当前光标处插入新字符
2180          * Replace the text at the current cursor selected or insert a new character at the current cursor position
2181          * 
2182          * @param   {String}    value  要插入的字符值
2183          * @returns {editormd}         返回editormd的实例对象
2184          */
2185         
2186         replaceSelection : function(value) {
2187             this.cm.replaceSelection(value);
2188
2189             return this;
2190         },
2191         
2192         /**
2193          * 在当前光标处插入新字符
2194          * Insert a new character at the current cursor position
2195          *
2196          * 同replaceSelection()方法
2197          * With the replaceSelection() method
2198          * 
2199          * @param   {String}    value  要插入的字符值
2200          * @returns {editormd}         返回editormd的实例对象
2201          */
2202         
2203         insertValue : function(value) {
2204             this.replaceSelection(value);
2205
2206             return this;
2207         },
2208         
2209         /**
2210          * 追加markdown
2211          * append Markdown to editor
2212          * 
2213          * @param   {String}    md     要追加的markdown源文档
2214          * @returns {editormd}         返回editormd的实例对象
2215          */
2216         
2217         appendMarkdown : function(md) {
2218             var settings = this.settings;
2219             var cm       = this.cm;
2220             
2221             cm.setValue(cm.getValue() + md);
2222             
2223             return this;
2224         },
2225         
2226         /**
2227          * 设置和传入编辑器的markdown源文档
2228          * Set Markdown source document
2229          * 
2230          * @param   {String}    md     要传入的markdown源文档
2231          * @returns {editormd}         返回editormd的实例对象
2232          */
2233         
2234         setMarkdown : function(md) {
2235             this.cm.setValue(md || this.settings.markdown);
2236             
2237             return this;
2238         },
2239         
2240         /**
2241          * 获取编辑器的markdown源文档
2242          * Set Editor.md markdown/CodeMirror value
2243          * 
2244          * @returns {editormd}         返回editormd的实例对象
2245          */
2246         
2247         getMarkdown : function() {
2248             return this.cm.getValue();
2249         },
2250         
2251         /**
2252          * 获取编辑器的源文档
2253          * Get CodeMirror value
2254          * 
2255          * @returns {editormd}         返回editormd的实例对象
2256          */
2257         
2258         getValue : function() {
2259             return this.cm.getValue();
2260         },
2261         
2262         /**
2263          * 设置编辑器的源文档
2264          * Set CodeMirror value
2265          * 
2266          * @param   {String}     value   set code/value/string/text
2267          * @returns {editormd}           返回editormd的实例对象
2268          */
2269         
2270         setValue : function(value) {
2271             this.cm.setValue(value);
2272             
2273             return this;
2274         },
2275         
2276         /**
2277          * 清空编辑器
2278          * Empty CodeMirror editor container
2279          * 
2280          * @returns {editormd}         返回editormd的实例对象
2281          */
2282         
2283         clear : function() {
2284             this.cm.setValue("");
2285             
2286             return this;
2287         },
2288         
2289         /**
2290          * 获取解析后存放在Textarea的HTML源码
2291          * Get parsed html code from Textarea
2292          * 
2293          * @returns {String}               返回HTML源码
2294          */
2295         
2296         getHTML : function() {
2297             if (!this.settings.saveHTMLToTextarea)
2298             {
2299                 alert("Error: settings.saveHTMLToTextarea == false");
2300
2301                 return false;
2302             }
2303             
2304             return this.htmlTextarea.val();
2305         },
2306         
2307         /**
2308          * getHTML()的别名
2309          * getHTML (alias)
2310          * 
2311          * @returns {String}           Return html code 返回HTML源码
2312          */
2313         
2314         getTextareaSavedHTML : function() {
2315             return this.getHTML();
2316         },
2317         
2318         /**
2319          * 获取预览窗口的HTML源码
2320          * Get html from preview container
2321          * 
2322          * @returns {editormd}         返回editormd的实例对象
2323          */
2324         
2325         getPreviewedHTML : function() {
2326             if (!this.settings.watch)
2327             {
2328                 alert("Error: settings.watch == false");
2329
2330                 return false;
2331             }
2332             
2333             return this.previewContainer.html();
2334         },
2335         
2336         /**
2337          * 开启实时预览
2338          * Enable real-time watching
2339          * 
2340          * @returns {editormd}         返回editormd的实例对象
2341          */
2342         
2343         watch : function(callback) {     
2344             var settings        = this.settings;
2345             
2346             if ($.inArray(settings.mode, ["gfm", "markdown"]) < 0)
2347             {
2348                 return this;
2349             }
2350             
2351             this.state.watching = settings.watch = true;
2352             this.preview.show();
2353             
2354             if (this.toolbar)
2355             {
2356                 var watchIcon   = settings.toolbarIconsClass.watch;
2357                 var unWatchIcon = settings.toolbarIconsClass.unwatch;
2358                 
2359                 var icon        = this.toolbar.find(".fa[name=watch]");
2360                 icon.parent().attr("title", settings.lang.toolbar.watch);
2361                 icon.removeClass(unWatchIcon).addClass(watchIcon);
2362             }
2363             
2364             this.codeMirror.css("border-right", "1px solid #ddd").width(this.editor.width() / 2); 
2365             
2366             timer = 0;
2367             
2368             this.save().resize();
2369             
2370             if (!settings.onwatch)
2371             {
2372                 settings.onwatch = callback || function() {};
2373             }
2374             
2375             $.proxy(settings.onwatch, this)();
2376             
2377             return this;
2378         },
2379         
2380         /**
2381          * 关闭实时预览
2382          * Disable real-time watching
2383          * 
2384          * @returns {editormd}         返回editormd的实例对象
2385          */
2386         
2387         unwatch : function(callback) {
2388             var settings        = this.settings;
2389             this.state.watching = settings.watch = false;
2390             this.preview.hide();
2391             
2392             if (this.toolbar) 
2393             {
2394                 var watchIcon   = settings.toolbarIconsClass.watch;
2395                 var unWatchIcon = settings.toolbarIconsClass.unwatch;
2396                 
2397                 var icon    = this.toolbar.find(".fa[name=watch]");
2398                 icon.parent().attr("title", settings.lang.toolbar.unwatch);
2399                 icon.removeClass(watchIcon).addClass(unWatchIcon);
2400             }
2401             
2402             this.codeMirror.css("border-right", "none").width(this.editor.width());
2403             
2404             this.resize();
2405             
2406             if (!settings.onunwatch)
2407             {
2408                 settings.onunwatch = callback || function() {};
2409             }
2410             
2411             $.proxy(settings.onunwatch, this)();
2412             
2413             return this;
2414         },
2415         
2416         /**
2417          * 显示编辑器
2418          * Show editor
2419          * 
2420          * @param   {Function} [callback=function()] 回调函数
2421          * @returns {editormd}                       返回editormd的实例对象
2422          */
2423         
2424         show : function(callback) {
2425             callback  = callback || function() {};
2426             
2427             var _this = this;
2428             this.editor.show(0, function() {
2429                 $.proxy(callback, _this)();
2430             });
2431             
2432             return this;
2433         },
2434         
2435         /**
2436          * 隐藏编辑器
2437          * Hide editor
2438          * 
2439          * @param   {Function} [callback=function()] 回调函数
2440          * @returns {editormd}                       返回editormd的实例对象
2441          */
2442         
2443         hide : function(callback) {
2444             callback  = callback || function() {};
2445             
2446             var _this = this;
2447             this.editor.hide(0, function() {
2448                 $.proxy(callback, _this)();
2449             });
2450             
2451             return this;
2452         },
2453         
2454         /**
2455          * 隐藏编辑器部分,只预览HTML
2456          * Enter preview html state
2457          * 
2458          * @returns {editormd}         返回editormd的实例对象
2459          */
2460         
2461         previewing : function() {
2462             
2463             var _this            = this;
2464             var editor           = this.editor;
2465             var preview          = this.preview;
2466             var toolbar          = this.toolbar;
2467             var settings         = this.settings;
2468             var codeMirror       = this.codeMirror;
2469             var previewContainer = this.previewContainer;
2470             
2471             if ($.inArray(settings.mode, ["gfm", "markdown"]) < 0) {
2472                 return this;
2473             }
2474             
2475             if (settings.toolbar && toolbar) {
2476                 toolbar.toggle();
2477                 toolbar.find(".fa[name=preview]").toggleClass("active");
2478             }
2479             
2480             codeMirror.toggle();
2481             
2482             var escHandle = function(event) {
2483                 if (event.shiftKey && event.keyCode === 27) {
2484                     _this.previewed();
2485                 }
2486             };
2487
2488             if (codeMirror.css("display") === "none") // 为了兼容Zepto,而不使用codeMirror.is(":hidden")
2489             {
2490                 this.state.preview = true;
2491
2492                 if (this.state.fullscreen) {
2493                     preview.css("background", "#fff");
2494                 }
2495                 
2496                 editor.find("." + this.classPrefix + "preview-close-btn").show().bind(editormd.mouseOrTouch("click", "touchend"), function(){
2497                     _this.previewed();
2498                 });
2499             
2500                 if (!settings.watch)
2501                 {
2502                     this.save();
2503                 } 
2504                 else 
2505                 {
2506                     previewContainer.css("padding", "");
2507                 }
2508                 
2509                 previewContainer.addClass(this.classPrefix + "preview-active");
2510
2511                 preview.show().css({
2512                     position  : "",
2513                     top       : 0,
2514                     width     : editor.width(),
2515                     height    : (settings.autoHeight && !this.state.fullscreen) ? "auto" : editor.height()
2516                 });
2517                 
2518                 if (this.state.loaded)
2519                 {
2520                     $.proxy(settings.onpreviewing, this)();
2521                 }
2522
2523                 $(window).bind("keyup", escHandle);
2524             } 
2525             else 
2526             {
2527                 $(window).unbind("keyup", escHandle);
2528                 this.previewed();
2529             }
2530         },
2531         
2532         /**
2533          * 显示编辑器部分,退出只预览HTML
2534          * Exit preview html state
2535          * 
2536          * @returns {editormd}         返回editormd的实例对象
2537          */
2538         
2539         previewed : function() {
2540             
2541             var editor           = this.editor;
2542             var preview          = this.preview;
2543             var toolbar          = this.toolbar;
2544             var settings         = this.settings;
2545             var previewContainer = this.previewContainer;
2546             var previewCloseBtn  = editor.find("." + this.classPrefix + "preview-close-btn");
2547
2548             this.state.preview   = false;
2549             
2550             this.codeMirror.show();
2551             
2552             if (settings.toolbar) {
2553                 toolbar.show();
2554             }
2555             
2556             preview[(settings.watch) ? "show" : "hide"]();
2557             
2558             previewCloseBtn.hide().unbind(editormd.mouseOrTouch("click", "touchend"));
2559                 
2560             previewContainer.removeClass(this.classPrefix + "preview-active");
2561                 
2562             if (settings.watch)
2563             {
2564                 previewContainer.css("padding", "20px");
2565             }
2566             
2567             preview.css({ 
2568                 background : null,
2569                 position   : "absolute",
2570                 width      : editor.width() / 2,
2571                 height     : (settings.autoHeight && !this.state.fullscreen) ? "auto" : editor.height() - toolbar.height(),
2572                 top        : (settings.toolbar)    ? toolbar.height() : 0
2573             });
2574
2575             if (this.state.loaded)
2576             {
2577                 $.proxy(settings.onpreviewed, this)();
2578             }
2579             
2580             return this;
2581         },
2582         
2583         /**
2584          * 编辑器全屏显示
2585          * Fullscreen show
2586          * 
2587          * @returns {editormd}         返回editormd的实例对象
2588          */
2589         
2590         fullscreen : function() {
2591             
2592             var _this            = this;
2593             var state            = this.state;
2594             var editor           = this.editor;
2595             var preview          = this.preview;
2596             var toolbar          = this.toolbar;
2597             var settings         = this.settings;
2598             var fullscreenClass  = this.classPrefix + "fullscreen";
2599             
2600             if (toolbar) {
2601                 toolbar.find(".fa[name=fullscreen]").parent().toggleClass("active"); 
2602             }
2603             
2604             var escHandle = function(event) {
2605                 if (!event.shiftKey && event.keyCode === 27) 
2606                 {
2607                     if (state.fullscreen)
2608                     {
2609                         _this.fullscreenExit();
2610                     }
2611                 }
2612             };
2613
2614             if (!editor.hasClass(fullscreenClass)) 
2615             {
2616                 state.fullscreen = true;
2617
2618                 $("html,body").css("overflow", "hidden");
2619                 
2620                 editor.css({
2621                     width    : $(window).width(),
2622                     height   : $(window).height()
2623                 }).addClass(fullscreenClass);
2624
2625                 this.resize();
2626     
2627                 $.proxy(settings.onfullscreen, this)();
2628
2629                 $(window).bind("keyup", escHandle);
2630             }
2631             else
2632             {           
2633                 $(window).unbind("keyup", escHandle); 
2634                 this.fullscreenExit();
2635             }
2636
2637             return this;
2638         },
2639         
2640         /**
2641          * 编辑器退出全屏显示
2642          * Exit fullscreen state
2643          * 
2644          * @returns {editormd}         返回editormd的实例对象
2645          */
2646         
2647         fullscreenExit : function() {
2648             
2649             var editor            = this.editor;
2650             var settings          = this.settings;
2651             var toolbar           = this.toolbar;
2652             var fullscreenClass   = this.classPrefix + "fullscreen";  
2653             
2654             this.state.fullscreen = false;
2655             
2656             if (toolbar) {
2657                 toolbar.find(".fa[name=fullscreen]").parent().removeClass("active"); 
2658             }
2659
2660             $("html,body").css("overflow", "");
2661
2662             editor.css({
2663                 width    : editor.data("oldWidth"),
2664                 height   : editor.data("oldHeight")
2665             }).removeClass(fullscreenClass);
2666
2667             this.resize();
2668             
2669             $.proxy(settings.onfullscreenExit, this)();
2670
2671             return this;
2672         },
2673         
2674         /**
2675          * 加载并执行插件
2676          * Load and execute the plugin
2677          * 
2678          * @param   {String}     name    plugin name / function name
2679          * @param   {String}     path    plugin load path
2680          * @returns {editormd}           返回editormd的实例对象
2681          */
2682         
2683         executePlugin : function(name, path) {
2684             
2685             var _this    = this;
2686             var cm       = this.cm;
2687             var settings = this.settings;
2688             
2689             path = settings.pluginPath + path;
2690             
2691             if (typeof define === "function") 
2692             {            
2693                 if (typeof this[name] === "undefined")
2694                 {
2695                     alert("Error: " + name + " plugin is not found, you are not load this plugin.");
2696                     
2697                     return this;
2698                 }
2699                 
2700                 this[name](cm);
2701                 
2702                 return this;
2703             }
2704             
2705             if ($.inArray(path, editormd.loadFiles.plugin) < 0)
2706             {
2707                 editormd.loadPlugin(path, function() {
2708                     editormd.loadPlugins[name] = _this[name];
2709                     _this[name](cm);
2710                 });
2711             }
2712             else
2713             {
2714                 $.proxy(editormd.loadPlugins[name], this)(cm);
2715             }
2716             
2717             return this;
2718         },
2719                 
2720         /**
2721          * 搜索替换
2722          * Search & replace
2723          * 
2724          * @param   {String}     command    CodeMirror serach commands, "find, fintNext, fintPrev, clearSearch, replace, replaceAll"
2725          * @returns {editormd}              return this
2726          */
2727         
2728         search : function(command) {
2729             var settings = this.settings;
2730             
2731             if (!settings.searchReplace)
2732             {
2733                 alert("Error: settings.searchReplace == false");
2734                 return this;
2735             }
2736             
2737             if (!settings.readOnly)
2738             {
2739                 this.cm.execCommand(command || "find");
2740             }
2741             
2742             return this;
2743         },
2744         
2745         searchReplace : function() {            
2746             this.search("replace");
2747             
2748             return this;
2749         },
2750         
2751         searchReplaceAll : function() {          
2752             this.search("replaceAll");
2753             
2754             return this;
2755         }
2756     };
2757     
2758     editormd.fn.init.prototype = editormd.fn; 
2759    
2760     /**
2761      * 锁屏
2762      * lock screen when dialog opening
2763      * 
2764      * @returns {void}
2765      */
2766
2767     editormd.dialogLockScreen = function() {
2768         var settings = this.settings || {dialogLockScreen : true};
2769         
2770         if (settings.dialogLockScreen) 
2771         {            
2772             $("html,body").css("overflow", "hidden");
2773             this.resize();
2774         }
2775     };
2776    
2777     /**
2778      * 显示透明背景层
2779      * Display mask layer when dialog opening
2780      * 
2781      * @param   {Object}     dialog    dialog jQuery object
2782      * @returns {void}
2783      */
2784     
2785     editormd.dialogShowMask = function(dialog) {
2786         var editor   = this.editor;
2787         var settings = this.settings || {dialogShowMask : true};
2788         
2789         dialog.css({
2790             top  : ($(window).height() - dialog.height()) / 2 + "px",
2791             left : ($(window).width()  - dialog.width())  / 2 + "px"
2792         });
2793
2794         if (settings.dialogShowMask) {
2795             editor.children("." + this.classPrefix + "mask").css("z-index", parseInt(dialog.css("z-index")) - 1).show();
2796         }
2797     };
2798
2799     editormd.toolbarHandlers = {
2800         undo : function() {
2801             this.cm.undo();
2802         },
2803         
2804         redo : function() {
2805             this.cm.redo();
2806         },
2807         
2808         bold : function() {
2809             var cm        = this.cm;
2810             var cursor    = cm.getCursor();
2811             var selection = cm.getSelection();
2812
2813             cm.replaceSelection("**" + selection + "**");
2814
2815             if(selection === "") {
2816                 cm.setCursor(cursor.line, cursor.ch + 2);
2817             }
2818         },
2819         
2820         del : function() {
2821             var cm        = this.cm;
2822             var cursor    = cm.getCursor();
2823             var selection = cm.getSelection();
2824
2825             cm.replaceSelection("~~" + selection + "~~");
2826
2827             if(selection === "") {
2828                 cm.setCursor(cursor.line, cursor.ch + 2);
2829             }
2830         },
2831
2832         italic : function() {
2833             var cm        = this.cm;
2834             var cursor    = cm.getCursor();
2835             var selection = cm.getSelection();
2836
2837             cm.replaceSelection("*" + selection + "*");
2838
2839             if(selection === "") {
2840                 cm.setCursor(cursor.line, cursor.ch + 1);
2841             }
2842         },
2843
2844         quote : function() {
2845             var cm        = this.cm;
2846             var cursor    = cm.getCursor();
2847             var selection = cm.getSelection();
2848
2849             if (cursor.ch !== 0)
2850             {
2851                 cm.setCursor(cursor.line, 0);
2852                 cm.replaceSelection("> " + selection);
2853                 cm.setCursor(cursor.line, cursor.ch + 2);
2854             }
2855             else
2856             {
2857                 cm.replaceSelection("> " + selection);
2858             }
2859
2860             //cm.replaceSelection("> " + selection);
2861             //cm.setCursor(cursor.line, (selection === "") ? cursor.ch + 2 : cursor.ch + selection.length + 2);
2862         },
2863         
2864         ucfirst : function() {
2865             var cm         = this.cm;
2866             var selection  = cm.getSelection();
2867             var selections = cm.listSelections();
2868
2869             cm.replaceSelection(editormd.firstUpperCase(selection));
2870             cm.setSelections(selections);
2871         },
2872         
2873         ucwords : function() {
2874             var cm         = this.cm;
2875             var selection  = cm.getSelection();
2876             var selections = cm.listSelections();
2877
2878             cm.replaceSelection(editormd.wordsFirstUpperCase(selection));
2879             cm.setSelections(selections);
2880         },
2881         
2882         uppercase : function() {
2883             var cm         = this.cm;
2884             var selection  = cm.getSelection();
2885             var selections = cm.listSelections();
2886
2887             cm.replaceSelection(selection.toUpperCase());
2888             cm.setSelections(selections);
2889         },
2890         
2891         lowercase : function() {
2892             var cm         = this.cm;
2893             var cursor     = cm.getCursor();
2894             var selection  = cm.getSelection();
2895             var selections = cm.listSelections();
2896             
2897             cm.replaceSelection(selection.toLowerCase());
2898             cm.setSelections(selections);
2899         },
2900
2901         h1 : function() {
2902             var cm        = this.cm;
2903             var cursor    = cm.getCursor();
2904             var selection = cm.getSelection();
2905
2906             if (cursor.ch !== 0)
2907             {
2908                 cm.setCursor(cursor.line, 0);
2909                 cm.replaceSelection("# " + selection);
2910                 cm.setCursor(cursor.line, cursor.ch + 2);
2911             }
2912             else
2913             {
2914                 cm.replaceSelection("# " + selection);
2915             }
2916         },
2917
2918         h2 : function() {
2919             var cm        = this.cm;
2920             var cursor    = cm.getCursor();
2921             var selection = cm.getSelection();
2922
2923             if (cursor.ch !== 0)
2924             {
2925                 cm.setCursor(cursor.line, 0);
2926                 cm.replaceSelection("## " + selection);
2927                 cm.setCursor(cursor.line, cursor.ch + 3);
2928             }
2929             else
2930             {
2931                 cm.replaceSelection("## " + selection);
2932             }
2933         },
2934
2935         h3 : function() {
2936             var cm        = this.cm;
2937             var cursor    = cm.getCursor();
2938             var selection = cm.getSelection();
2939
2940             if (cursor.ch !== 0)
2941             {
2942                 cm.setCursor(cursor.line, 0);
2943                 cm.replaceSelection("### " + selection);
2944                 cm.setCursor(cursor.line, cursor.ch + 4);
2945             }
2946             else
2947             {
2948                 cm.replaceSelection("### " + selection);
2949             }
2950         },
2951
2952         h4 : function() {
2953             var cm        = this.cm;
2954             var cursor    = cm.getCursor();
2955             var selection = cm.getSelection();
2956
2957             if (cursor.ch !== 0)
2958             {
2959                 cm.setCursor(cursor.line, 0);
2960                 cm.replaceSelection("#### " + selection);
2961                 cm.setCursor(cursor.line, cursor.ch + 5);
2962             }
2963             else
2964             {
2965                 cm.replaceSelection("#### " + selection);
2966             }
2967         },
2968
2969         h5 : function() {
2970             var cm        = this.cm;
2971             var cursor    = cm.getCursor();
2972             var selection = cm.getSelection();
2973
2974             if (cursor.ch !== 0)
2975             {
2976                 cm.setCursor(cursor.line, 0);
2977                 cm.replaceSelection("##### " + selection);
2978                 cm.setCursor(cursor.line, cursor.ch + 6);
2979             }
2980             else
2981             {
2982                 cm.replaceSelection("##### " + selection);
2983             }
2984         },
2985
2986         h6 : function() {
2987             var cm        = this.cm;
2988             var cursor    = cm.getCursor();
2989             var selection = cm.getSelection();
2990
2991             if (cursor.ch !== 0)
2992             {
2993                 cm.setCursor(cursor.line, 0);
2994                 cm.replaceSelection("###### " + selection);
2995                 cm.setCursor(cursor.line, cursor.ch + 7);
2996             }
2997             else
2998             {
2999                 cm.replaceSelection("###### " + selection);
3000             }
3001         },
3002
3003         "list-ul" : function() {
3004             var cm        = this.cm;
3005             var cursor    = cm.getCursor();
3006             var selection = cm.getSelection();
3007
3008             if (selection === "") 
3009             {
3010                 cm.replaceSelection("- " + selection);
3011             } 
3012             else 
3013             {
3014                 var selectionText = selection.split("\n");
3015
3016                 for (var i = 0, len = selectionText.length; i < len; i++) 
3017                 {
3018                     selectionText[i] = (selectionText[i] === "") ? "" : "- " + selectionText[i];
3019                 }
3020
3021                 cm.replaceSelection(selectionText.join("\n"));
3022             }
3023         },
3024
3025         "list-ol" : function() {
3026             var cm        = this.cm;
3027             var cursor    = cm.getCursor();
3028             var selection = cm.getSelection();
3029
3030             if(selection === "") 
3031             {
3032                 cm.replaceSelection("1. " + selection);
3033             }
3034             else
3035             {
3036                 var selectionText = selection.split("\n");
3037
3038                 for (var i = 0, len = selectionText.length; i < len; i++) 
3039                 {
3040                     selectionText[i] = (selectionText[i] === "") ? "" : (i+1) + ". " + selectionText[i];
3041                 }
3042
3043                 cm.replaceSelection(selectionText.join("\n"));
3044             }
3045         },
3046
3047         hr : function() {
3048             var cm        = this.cm;
3049             var cursor    = cm.getCursor();
3050             var selection = cm.getSelection();
3051
3052             cm.replaceSelection(((cursor.ch !== 0) ? "\n\n" : "\n") + "------------\n\n");
3053         },
3054
3055         tex : function() {
3056             if (!this.settings.tex)
3057             {
3058                 alert("settings.tex === false");
3059                 return this;
3060             }
3061             
3062             var cm        = this.cm;
3063             var cursor    = cm.getCursor();
3064             var selection = cm.getSelection();
3065
3066             cm.replaceSelection("$$" + selection + "$$");
3067
3068             if(selection === "") {
3069                 cm.setCursor(cursor.line, cursor.ch + 2);
3070             }
3071         },
3072
3073         link : function() {
3074             this.executePlugin("linkDialog", "link-dialog/link-dialog");
3075         },
3076
3077         "reference-link" : function() {
3078             this.executePlugin("referenceLinkDialog", "reference-link-dialog/reference-link-dialog");
3079         },
3080
3081         pagebreak : function() {
3082             if (!this.settings.pageBreak)
3083             {
3084                 alert("settings.pageBreak === false");
3085                 return this;
3086             }
3087             
3088             var cm        = this.cm;
3089             var selection = cm.getSelection();
3090
3091             cm.replaceSelection("\r\n[========]\r\n");
3092         },
3093
3094         image : function() {
3095             this.executePlugin("imageDialog", "image-dialog/image-dialog");
3096         },
3097         
3098         code : function() {
3099             var cm        = this.cm;
3100             var cursor    = cm.getCursor();
3101             var selection = cm.getSelection();
3102
3103             cm.replaceSelection("`" + selection + "`");
3104
3105             if (selection === "") {
3106                 cm.setCursor(cursor.line, cursor.ch + 1);
3107             }
3108         },
3109
3110         "code-block" : function() {
3111             this.executePlugin("codeBlockDialog", "code-block-dialog/code-block-dialog");            
3112         },
3113
3114         "preformatted-text" : function() {
3115             this.executePlugin("preformattedTextDialog", "preformatted-text-dialog/preformatted-text-dialog");
3116         },
3117         
3118         table : function() {
3119             this.executePlugin("tableDialog", "table-dialog/table-dialog");         
3120         },
3121         
3122         datetime : function() {
3123             var cm        = this.cm;
3124             var selection = cm.getSelection();
3125             var date      = new Date();
3126             var langName  = this.settings.lang.name;
3127             var datefmt   = editormd.dateFormat() + " " + editormd.dateFormat((langName === "zh-cn" || langName === "zh-tw") ? "cn-week-day" : "week-day");
3128
3129             cm.replaceSelection(datefmt);
3130         },
3131         
3132         emoji : function() {
3133             this.executePlugin("emojiDialog", "emoji-dialog/emoji-dialog");
3134         },
3135                 
3136         "html-entities" : function() {
3137             this.executePlugin("htmlEntitiesDialog", "html-entities-dialog/html-entities-dialog");
3138         },
3139                 
3140         "goto-line" : function() {
3141             this.executePlugin("gotoLineDialog", "goto-line-dialog/goto-line-dialog");
3142         },
3143
3144         watch : function() {    
3145             this[this.settings.watch ? "unwatch" : "watch"]();
3146         },
3147
3148         preview : function() {
3149             this.previewing();
3150         },
3151
3152         fullscreen : function() {
3153             this.fullscreen();
3154         },
3155
3156         clear : function() {
3157             this.clear();
3158         },
3159         
3160         search : function() {
3161             this.search();
3162         },
3163
3164         help : function() {
3165             this.executePlugin("helpDialog", "help-dialog/help-dialog");
3166         },
3167
3168         info : function() {
3169             this.showInfoDialog();
3170         }
3171     };
3172     
3173     var isMac = navigator.platform.toUpperCase().indexOf('MAC')>=0;
3174     var key = isMac ? "Cmd" : "Ctrl";
3175     
3176     editormd.keyMaps = {
3177         [key + "-1"]       : "h1",
3178         [key + "-2"]       : "h2",
3179         [key + "-3"]       : "h3",
3180         [key + "-4"]       : "h4",
3181         [key + "-5"]       : "h5",
3182         [key + "-6"]       : "h6",
3183         [key + "-B"]       : "bold",  // if this is string ==  editormd.toolbarHandlers.xxxx
3184         [key + "-D"]       : "datetime",
3185         
3186         [key + "Ctrl-E"]       : function() { // emoji
3187             var cm        = this.cm;
3188             var cursor    = cm.getCursor();
3189             var selection = cm.getSelection();
3190             
3191             if (!this.settings.emoji)
3192             {
3193                 alert("Error: settings.emoji == false");
3194                 return ;
3195             }
3196
3197             cm.replaceSelection(":" + selection + ":");
3198
3199             if (selection === "") {
3200                 cm.setCursor(cursor.line, cursor.ch + 1);
3201             }
3202         },
3203         [key + "-Alt-G"]   : "goto-line",
3204         [key + "-H"]       : "hr",
3205         [key + "-I"]       : "italic",
3206         [key + "-K"]       : "code",
3207         
3208         "Ctrl-L"        : function() {
3209             var cm        = this.cm;
3210             var cursor    = cm.getCursor();
3211             var selection = cm.getSelection();
3212             
3213             var title = (selection === "") ? "" : " \""+selection+"\"";
3214
3215             cm.replaceSelection("[" + selection + "]("+title+")");
3216
3217             if (selection === "") {
3218                 cm.setCursor(cursor.line, cursor.ch + 1);
3219             }
3220         },
3221         [key + "-U"]         : "list-ul",
3222         
3223         "Shift-Ctrl-A"   : function() {
3224             var cm        = this.cm;
3225             var cursor    = cm.getCursor();
3226             var selection = cm.getSelection();
3227             
3228             if (!this.settings.atLink)
3229             {
3230                 alert("Error: settings.atLink == false");
3231                 return ;
3232             }
3233
3234             cm.replaceSelection("@" + selection);
3235
3236             if (selection === "") {
3237                 cm.setCursor(cursor.line, cursor.ch + 1);
3238             }
3239         },
3240         
3241         ["Shift" + key + "-C"]     : "code",
3242         ["Shift" + key + "Q"]     : "quote",
3243         ["Shift" + key + "S"]     : "del",
3244         ["Shift" + key + "K"]     : "tex",  // KaTeX
3245         
3246         "Shift-Alt-C"      : function() {
3247             var cm        = this.cm;
3248             var cursor    = cm.getCursor();
3249             var selection = cm.getSelection();
3250             
3251             cm.replaceSelection(["```", selection, "```"].join("\n"));
3252
3253             if (selection === "") {
3254                 cm.setCursor(cursor.line, cursor.ch + 3);
3255             } 
3256         },
3257         
3258         ["Shift-" + key + "-Alt-C"]      : "code-block",
3259         ["Shift-" + key + "-H"]          : "html-entities",
3260         "Shift-Alt-H"                    : "help",
3261         ["Shift-" + key + "-E"]          : "emoji",
3262         ["Shift-" + key + "-U"]          : "uppercase",
3263         "Shift-Alt-U"                    : "ucwords",
3264         ["Shift-" + key + "-Alt-U"]      : "ucfirst",
3265         "Shift-Alt-L"                    : "lowercase",
3266         
3267         ["Shift-" + key + "-I"]          : function() {
3268             var cm        = this.cm;
3269             var cursor    = cm.getCursor();
3270             var selection = cm.getSelection();
3271             
3272             var title = (selection === "") ? "" : " \""+selection+"\"";
3273
3274             cm.replaceSelection("![" + selection + "]("+title+")");
3275
3276             if (selection === "") {
3277                 cm.setCursor(cursor.line, cursor.ch + 4);
3278             }
3279         },
3280         
3281         ["Shift-" + key + "-Alt-I"]     : "image",
3282         ["Shift-" + key + "-L"]         : "link",
3283         ["Shift-" + key + "-O"]         : "list-ol",
3284         ["Shift-" + key + "-P"]         : "preformatted-text",
3285         ["Shift-" + key + "-T"]         : "table",
3286         "Shift-Alt-P"                   : "pagebreak",
3287         "F9"                            : "watch",
3288         "F10"                           : "preview",
3289         "F11"                           : "fullscreen",
3290     };
3291     
3292     /**
3293      * 清除字符串两边的空格
3294      * Clear the space of strings both sides.
3295      * 
3296      * @param   {String}    str            string
3297      * @returns {String}                   trimed string    
3298      */
3299     
3300     var trim = function(str) {
3301         return (!String.prototype.trim) ? str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, "") : str.trim();
3302     };
3303     
3304     editormd.trim = trim;
3305     
3306     /**
3307      * 所有单词首字母大写
3308      * Words first to uppercase
3309      * 
3310      * @param   {String}    str            string
3311      * @returns {String}                   string
3312      */
3313     
3314     var ucwords = function (str) {
3315         return str.toLowerCase().replace(/\b(\w)|\s(\w)/g, function($1) {  
3316             return $1.toUpperCase();
3317         });
3318     };
3319     
3320     editormd.ucwords = editormd.wordsFirstUpperCase = ucwords;
3321     
3322     /**
3323      * 字符串首字母大写
3324      * Only string first char to uppercase
3325      * 
3326      * @param   {String}    str            string
3327      * @returns {String}                   string
3328      */
3329     
3330     var firstUpperCase = function(str) {        
3331         return str.toLowerCase().replace(/\b(\w)/, function($1){
3332             return $1.toUpperCase();
3333         });
3334     };
3335     
3336     var ucfirst = firstUpperCase;
3337     
3338     editormd.firstUpperCase = editormd.ucfirst = firstUpperCase;
3339     
3340     editormd.urls = {
3341         atLinkBase : "https://github.com/"
3342     };
3343     
3344     editormd.regexs = {
3345         atLink        : /@(\w+)/g,
3346         email         : /(\w+)@(\w+)\.(\w+)\.?(\w+)?/g,
3347         emailLink     : /(mailto:)?([\w\.\_]+)@(\w+)\.(\w+)\.?(\w+)?/g,
3348         emoji         : /:([\w\+-]+):/g,
3349         emojiDatetime : /(\d{1,2}:\d{1,2}:\d{1,2})/g,
3350         twemoji       : /:(tw-([\w]+)-?(\w+)?):/g,
3351         fontAwesome   : /:(fa-([\w]+)(-(\w+)){0,}):/g,
3352         editormdLogo  : /:(editormd-logo-?(\w+)?):/g,
3353         pageBreak     : /^\[[=]{8,}\]$/
3354     };
3355
3356     // Emoji graphics files url path
3357     editormd.emoji     = {
3358         path  : "http://www.emoji-cheat-sheet.com/graphics/emojis/",
3359         ext   : ".png"
3360     };
3361
3362     // Twitter Emoji (Twemoji)  graphics files url path    
3363     editormd.twemoji = {
3364         path : "http://twemoji.maxcdn.com/36x36/",
3365         ext  : ".png"
3366     };
3367
3368     /**
3369      * 自定义marked的解析器
3370      * Custom Marked renderer rules
3371      * 
3372      * @param   {Array}    markdownToC     传入用于接收TOC的数组
3373      * @returns {Renderer} markedRenderer  返回marked的Renderer自定义对象
3374      */
3375
3376     editormd.markedRenderer = function(markdownToC, options) {
3377         var defaults = {
3378             toc                  : true,           // Table of contents
3379             tocm                 : false,
3380             tocStartLevel        : 1,              // Said from H1 to create ToC  
3381             pageBreak            : true,
3382             atLink               : true,           // for @link
3383             emailLink            : true,           // for mail address auto link
3384             taskList             : false,          // Enable Github Flavored Markdown task lists
3385             emoji                : false,          // :emoji: , Support Twemoji, fontAwesome, Editor.md logo emojis.
3386             tex                  : false,          // TeX(LaTeX), based on KaTeX
3387             flowChart            : false,          // flowChart.js only support IE9+
3388             sequenceDiagram      : false,          // sequenceDiagram.js only support IE9+
3389         };
3390         
3391         var settings        = $.extend(defaults, options || {});    
3392         var marked          = editormd.$marked;
3393         var markedRenderer  = new marked.Renderer();
3394         markdownToC         = markdownToC || [];        
3395             
3396         var regexs          = editormd.regexs;
3397         var atLinkReg       = regexs.atLink;
3398         var emojiReg        = regexs.emoji;
3399         var emailReg        = regexs.email;
3400         var emailLinkReg    = regexs.emailLink;
3401         var twemojiReg      = regexs.twemoji;
3402         var faIconReg       = regexs.fontAwesome;
3403         var editormdLogoReg = regexs.editormdLogo;
3404         var pageBreakReg    = regexs.pageBreak;
3405
3406         markedRenderer.emoji = function(text) {
3407             
3408             text = text.replace(editormd.regexs.emojiDatetime, function($1) {           
3409                 return $1.replace(/:/g, "&#58;");
3410             });
3411             
3412             var matchs = text.match(emojiReg);
3413
3414             if (!matchs || !settings.emoji) {
3415                 return text;
3416             }
3417
3418             for (var i = 0, len = matchs.length; i < len; i++)
3419             {            
3420                 if (matchs[i] === ":+1:") {
3421                     matchs[i] = ":\\+1:";
3422                 }
3423
3424                 text = text.replace(new RegExp(matchs[i]), function($1, $2){
3425                     var faMatchs = $1.match(faIconReg);
3426                     var name     = $1.replace(/:/g, "");
3427
3428                     if (faMatchs)
3429                     {                        
3430                         for (var fa = 0, len1 = faMatchs.length; fa < len1; fa++)
3431                         {
3432                             var faName = faMatchs[fa].replace(/:/g, "");
3433                             
3434                             return "<i class=\"fa " + faName + " fa-emoji\" title=\"" + faName.replace("fa-", "") + "\"></i>";
3435                         }
3436                     }
3437                     else
3438                     {
3439                         var emdlogoMathcs = $1.match(editormdLogoReg);
3440                         var twemojiMatchs = $1.match(twemojiReg);
3441
3442                         if (emdlogoMathcs)                                        
3443                         {                            
3444                             for (var x = 0, len2 = emdlogoMathcs.length; x < len2; x++)
3445                             {
3446                                 var logoName = emdlogoMathcs[x].replace(/:/g, "");
3447                                 return "<i class=\"" + logoName + "\" title=\"Editor.md logo (" + logoName + ")\"></i>";
3448                             }
3449                         }
3450                         else if (twemojiMatchs) 
3451                         {
3452                             for (var t = 0, len3 = twemojiMatchs.length; t < len3; t++)
3453                             {
3454                                 var twe = twemojiMatchs[t].replace(/:/g, "").replace("tw-", "");
3455                                 return "<img src=\"" + editormd.twemoji.path + twe + editormd.twemoji.ext + "\" title=\"twemoji-" + twe + "\" alt=\"twemoji-" + twe + "\" class=\"emoji twemoji\" />";
3456                             }
3457                         }
3458                         else
3459                         {
3460                             var src = (name === "+1") ? "plus1" : name;
3461                             src     = (src === "black_large_square") ? "black_square" : src;
3462                             src     = (src === "moon") ? "waxing_gibbous_moon" : src;
3463
3464                             return "<img src=\"" + editormd.emoji.path + src + editormd.emoji.ext + "\" class=\"emoji\" title=\"&#58;" + name + "&#58;\" alt=\"&#58;" + name + "&#58;\" />";
3465                         }
3466                     }
3467                 });
3468             }
3469
3470             return text;
3471         };
3472
3473         markedRenderer.atLink = function(text) {
3474
3475             if (atLinkReg.test(text))
3476             { 
3477                 if (settings.atLink) 
3478                 {
3479                     text = text.replace(emailReg, function($1, $2, $3, $4) {
3480                         return $1.replace(/@/g, "_#_&#64;_#_");
3481                     });
3482
3483                     text = text.replace(atLinkReg, function($1, $2) {
3484                         return "<a href=\"" + editormd.urls.atLinkBase + "" + $2 + "\" title=\"&#64;" + $2 + "\" class=\"at-link\">" + $1 + "</a>";
3485                     }).replace(/_#_&#64;_#_/g, "@");
3486                 }
3487                 
3488                 if (settings.emailLink)
3489                 {
3490                     text = text.replace(emailLinkReg, function($1, $2, $3, $4, $5) {
3491                         return (!$2 && $.inArray($5, "jpg|jpeg|png|gif|webp|ico|icon|pdf".split("|")) < 0) ? "<a href=\"mailto:" + $1 + "\">"+$1+"</a>" : $1;
3492                     });
3493                 }
3494
3495                 return text;
3496             }
3497
3498             return text;
3499         };
3500                 
3501         markedRenderer.link = function (href, title, text) {
3502
3503             if (this.options.sanitize) {
3504                 try {
3505                     var prot = decodeURIComponent(unescape(href)).replace(/[^\w:]/g,"").toLowerCase();
3506                 } catch(e) {
3507                     return "";
3508                 }
3509
3510                 if (prot.indexOf("javascript:") === 0) {
3511                     return "";
3512                 }
3513             }
3514
3515             var out = "<a href=\"" + href + "\"";
3516             
3517             if (atLinkReg.test(title) || atLinkReg.test(text))
3518             {
3519                 if (title)
3520                 {
3521                     out += " title=\"" + title.replace(/@/g, "&#64;");
3522                 }
3523                 
3524                 return out + "\">" + text.replace(/@/g, "&#64;") + "</a>";
3525             }
3526
3527             if (title) {
3528                 out += " title=\"" + title + "\"";
3529             }
3530
3531             out += ">" + text + "</a>";
3532
3533             return out;
3534         };
3535         
3536         markedRenderer.heading = function(text, level, raw) {
3537                     
3538             var linkText       = text;
3539             var hasLinkReg     = /\s*\<a\s*href\=\"(.*)\"\s*([^\>]*)\>(.*)\<\/a\>\s*/;
3540             var getLinkTextReg = /\s*\<a\s*([^\>]+)\>([^\>]*)\<\/a\>\s*/g;
3541
3542             if (hasLinkReg.test(text)) 
3543             {
3544                 var tempText = [];
3545                 text         = text.split(/\<a\s*([^\>]+)\>([^\>]*)\<\/a\>/);
3546
3547                 for (var i = 0, len = text.length; i < len; i++)
3548                 {
3549                     tempText.push(text[i].replace(/\s*href\=\"(.*)\"\s*/g, ""));
3550                 }
3551
3552                 text = tempText.join(" ");
3553             }
3554             
3555             text = trim(text);
3556             
3557             var escapedText    = text.toLowerCase().replace(/[^\w]+/g, "-");
3558             var toc = {
3559                 text  : text,
3560                 level : level,
3561                 slug  : escapedText
3562             };
3563             
3564             var isChinese = /^[\u4e00-\u9fa5]+$/.test(text);
3565             var id        = (isChinese) ? escape(text).replace(/\%/g, "") : text.toLowerCase().replace(/[^\w]+/g, "-");
3566
3567             markdownToC.push(toc);
3568             
3569             var headingHTML = "<h" + level + " id=\"h"+ level + "-" + this.options.headerPrefix + id +"\">";
3570             
3571             headingHTML    += "<a name=\"" + text + "\" class=\"reference-link\"></a>";
3572             headingHTML    += "<span class=\"header-link octicon octicon-link\"></span>";
3573             headingHTML    += (hasLinkReg) ? this.atLink(this.emoji(linkText)) : this.atLink(this.emoji(text));
3574             headingHTML    += "</h" + level + ">";
3575
3576             return headingHTML;
3577         };
3578         
3579         markedRenderer.pageBreak = function(text) {
3580             if (pageBreakReg.test(text) && settings.pageBreak)
3581             {
3582                 text = "<hr style=\"page-break-after:always;\" class=\"page-break editormd-page-break\" />";
3583             }
3584             
3585             return text;
3586         };
3587
3588         markedRenderer.paragraph = function(text) {
3589             var isTeXInline     = /\$\$(.*)\$\$/g.test(text);
3590             var isTeXLine       = /^\$\$(.*)\$\$$/.test(text);
3591             var isTeXAddClass   = (isTeXLine)     ? " class=\"" + editormd.classNames.tex + "\"" : "";
3592             var isToC           = (settings.tocm) ? /^(\[TOC\]|\[TOCM\])$/.test(text) : /^\[TOC\]$/.test(text);
3593             var isToCMenu       = /^\[TOCM\]$/.test(text);
3594             
3595             if (!isTeXLine && isTeXInline) 
3596             {
3597                 text = text.replace(/(\$\$([^\$]*)\$\$)+/g, function($1, $2) {
3598                     return "<span class=\"" + editormd.classNames.tex + "\">" + $2.replace(/\$/g, "") + "</span>";
3599                 });
3600             } 
3601             else 
3602             {
3603                 text = (isTeXLine) ? text.replace(/\$/g, "") : text;
3604             }
3605             
3606             var tocHTML = "<div class=\"markdown-toc editormd-markdown-toc\">" + text + "</div>";
3607             
3608             return (isToC) ? ( (isToCMenu) ? "<div class=\"editormd-toc-menu\">" + tocHTML + "</div><br/>" : tocHTML )
3609                            : ( (pageBreakReg.test(text)) ? this.pageBreak(text) : "<p" + isTeXAddClass + ">" + this.atLink(this.emoji(text)) + "</p>\n" );
3610         };
3611
3612         markedRenderer.code = function (code, lang, escaped) { 
3613
3614             if (lang === "seq" || lang === "sequence")
3615             {
3616                 return "<div class=\"sequence-diagram\">" + code + "</div>";
3617             } 
3618             else if ( lang === "flow")
3619             {
3620                 return "<div class=\"flowchart\">" + code + "</div>";
3621             } 
3622             else if ( lang === "math" || lang === "latex" || lang === "katex")
3623             {
3624                 return "<p class=\"" + editormd.classNames.tex + "\">" + code + "</p>";
3625             } 
3626             else 
3627             {
3628
3629                 return marked.Renderer.prototype.code.apply(this, arguments);
3630             }
3631         };
3632
3633         markedRenderer.tablecell = function(content, flags) {
3634             var type = (flags.header) ? "th" : "td";
3635             var tag  = (flags.align)  ? "<" + type +" style=\"text-align:" + flags.align + "\">" : "<" + type + ">";
3636             
3637             return tag + this.atLink(this.emoji(content)) + "</" + type + ">\n";
3638         };
3639
3640         markedRenderer.listitem = function(text) {
3641             if (settings.taskList && /^\s*\[[x\s]\]\s*/.test(text)) 
3642             {
3643                 text = text.replace(/^\s*\[\s\]\s*/, "<input type=\"checkbox\" class=\"task-list-item-checkbox\" /> ")
3644                            .replace(/^\s*\[x\]\s*/,  "<input type=\"checkbox\" class=\"task-list-item-checkbox\" checked disabled /> ");
3645
3646                 return "<li style=\"list-style: none;\">" + this.atLink(this.emoji(text)) + "</li>";
3647             }
3648             else 
3649             {
3650                 return "<li>" + this.atLink(this.emoji(text)) + "</li>";
3651             }
3652         };
3653         
3654         return markedRenderer;
3655     };
3656     
3657     /**
3658      *
3659      * 生成TOC(Table of Contents)
3660      * Creating ToC (Table of Contents)
3661      * 
3662      * @param   {Array}    toc             从marked获取的TOC数组列表
3663      * @param   {Element}  container       插入TOC的容器元素
3664      * @param   {Integer}  startLevel      Hx 起始层级
3665      * @returns {Object}   tocContainer    返回ToC列表容器层的jQuery对象元素
3666      */
3667     
3668     editormd.markdownToCRenderer = function(toc, container, tocDropdown, startLevel) {
3669         
3670         var html        = "";    
3671         var lastLevel   = 0;
3672         var classPrefix = this.classPrefix;
3673         
3674         startLevel      = startLevel  || 1;
3675         
3676         for (var i = 0, len = toc.length; i < len; i++) 
3677         {
3678             var text  = toc[i].text;
3679             var level = toc[i].level;
3680             
3681             if (level < startLevel) {
3682                 continue;
3683             }
3684             
3685             if (level > lastLevel) 
3686             {
3687                 html += "";
3688             }
3689             else if (level < lastLevel) 
3690             {
3691                 html += (new Array(lastLevel - level + 2)).join("</ul></li>");
3692             } 
3693             else 
3694             {
3695                 html += "</ul></li>";
3696             }
3697
3698             html += "<li><a class=\"toc-level-" + level + "\" href=\"#" + text + "\" level=\"" + level + "\">" + text + "</a><ul>";
3699             lastLevel = level;
3700         }
3701         
3702         var tocContainer = container.find(".markdown-toc");
3703         
3704         if ((tocContainer.length < 1 && container.attr("previewContainer") === "false"))
3705         {
3706             var tocHTML = "<div class=\"markdown-toc " + classPrefix + "markdown-toc\"></div>";
3707             
3708             tocHTML = (tocDropdown) ? "<div class=\"" + classPrefix + "toc-menu\">" + tocHTML + "</div>" : tocHTML;
3709             
3710             container.html(tocHTML);
3711             
3712             tocContainer = container.find(".markdown-toc");
3713         }
3714         
3715         if (tocDropdown)
3716         {
3717             tocContainer.wrap("<div class=\"" + classPrefix + "toc-menu\"></div><br/>");
3718         }
3719         
3720         tocContainer.html("<ul class=\"markdown-toc-list\"></ul>").children(".markdown-toc-list").html(html.replace(/\r?\n?\<ul\>\<\/ul\>/g, ""));
3721         
3722         return tocContainer;
3723     };
3724     
3725     /**
3726      *
3727      * 生成TOC下拉菜单
3728      * Creating ToC dropdown menu
3729      * 
3730      * @param   {Object}   container       插入TOC的容器jQuery对象元素
3731      * @param   {String}   tocTitle        ToC title
3732      * @returns {Object}                   return toc-menu object
3733      */
3734     
3735     editormd.tocDropdownMenu = function(container, tocTitle) {
3736         
3737         tocTitle      = tocTitle || "Table of Contents";
3738         
3739         var zindex    = 400;
3740         var tocMenus  = container.find("." + this.classPrefix + "toc-menu");
3741
3742         tocMenus.each(function() {
3743             var $this  = $(this);
3744             var toc    = $this.children(".markdown-toc");
3745             var icon   = "<i class=\"fa fa-angle-down\"></i>";
3746             var btn    = "<a href=\"javascript:;\" class=\"toc-menu-btn\">" + icon + tocTitle + "</a>";
3747             var menu   = toc.children("ul");            
3748             var list   = menu.find("li");
3749             
3750             toc.append(btn);
3751             
3752             list.first().before("<li><h1>" + tocTitle + " " + icon + "</h1></li>");
3753             
3754             $this.mouseover(function(){
3755                 menu.show();
3756
3757                 list.each(function(){
3758                     var li = $(this);
3759                     var ul = li.children("ul");
3760
3761                     if (ul.html() === "")
3762                     {
3763                         ul.remove();
3764                     }
3765
3766                     if (ul.length > 0 && ul.html() !== "")
3767                     {
3768                         var firstA = li.children("a").first();
3769
3770                         if (firstA.children(".fa").length < 1)
3771                         {
3772                             firstA.append( $(icon).css({ float:"right", paddingTop:"4px" }) );
3773                         }
3774                     }
3775
3776                     li.mouseover(function(){
3777                         ul.css("z-index", zindex).show();
3778                         zindex += 1;
3779                     }).mouseleave(function(){
3780                         ul.hide();
3781                     });
3782                 });
3783             }).mouseleave(function(){
3784                 menu.hide();
3785             }); 
3786         });       
3787         
3788         return tocMenus;
3789     };
3790     
3791     /**
3792      * 简单地过滤指定的HTML标签
3793      * Filter custom html tags
3794      * 
3795      * @param   {String}   html          要过滤HTML
3796      * @param   {String}   filters       要过滤的标签
3797      * @returns {String}   html          返回过滤的HTML
3798      */
3799     
3800     editormd.filterHTMLTags = function(html, filters) {
3801         
3802         if (typeof html !== "string") {
3803             html = new String(html);
3804         }
3805             
3806         if (typeof filters !== "string") {
3807             return html;
3808         }
3809
3810         var expression = filters.split("|");
3811         var filterTags = expression[0].split(",");
3812         var attrs      = expression[1];
3813
3814         for (var i = 0, len = filterTags.length; i < len; i++)
3815         {
3816             var tag = filterTags[i];
3817
3818             html = html.replace(new RegExp("\<\s*" + tag + "\s*([^\>]*)\>([^\>]*)\<\s*\/" + tag + "\s*\>", "igm"), "");
3819         }
3820         
3821         //return html;
3822
3823         if (typeof attrs !== "undefined")
3824         {
3825             var htmlTagRegex = /\<(\w+)\s*([^\>]*)\>([^\>]*)\<\/(\w+)\>/ig;
3826
3827             if (attrs === "*")
3828             {
3829                 html = html.replace(htmlTagRegex, function($1, $2, $3, $4, $5) {
3830                     return "<" + $2 + ">" + $4 + "</" + $5 + ">";
3831                 });         
3832             }
3833             else if (attrs === "on*")
3834             {
3835                 html = html.replace(htmlTagRegex, function($1, $2, $3, $4, $5) {
3836                     var el = $("<" + $2 + ">" + $4 + "</" + $5 + ">");
3837                     var _attrs = $($1)[0].attributes;
3838                     var $attrs = {};
3839                     
3840                     $.each(_attrs, function(i, e) {
3841                         if (e.nodeName !== '"') $attrs[e.nodeName] = e.nodeValue;
3842                     });
3843                     
3844                     $.each($attrs, function(i) {                        
3845                         if (i.indexOf("on") === 0) {
3846                             delete $attrs[i];
3847                         }
3848                     });
3849                     
3850                     el.attr($attrs);
3851                     
3852                     var text = (typeof el[1] !== "undefined") ? $(el[1]).text() : "";
3853
3854                     return el[0].outerHTML + text;
3855                 });
3856             }
3857             else
3858             {
3859                 html = html.replace(htmlTagRegex, function($1, $2, $3, $4) {
3860                     var filterAttrs = attrs.split(",");
3861                     var el = $($1);
3862                     el.html($4);
3863
3864                     $.each(filterAttrs, function(i) {
3865                         el.attr(filterAttrs[i], null);
3866                     });
3867
3868                     return el[0].outerHTML;
3869                 });
3870             }
3871         }
3872         
3873         return html;
3874     };
3875     
3876     /**
3877      * 将Markdown文档解析为HTML用于前台显示
3878      * Parse Markdown to HTML for Font-end preview.
3879      * 
3880      * @param   {String}   id            用于显示HTML的对象ID
3881      * @param   {Object}   [options={}]  配置选项,可选
3882      * @returns {Object}   div           返回jQuery对象元素
3883      */
3884     
3885     editormd.markdownToHTML = function(id, options) {
3886         var defaults = {
3887             gfm                  : true,
3888             toc                  : true,
3889             tocm                 : false,
3890             tocStartLevel        : 1,
3891             tocTitle             : "目录",
3892             tocDropdown          : false,
3893             tocContainer         : "",
3894             markdown             : "",
3895             markdownSourceCode   : false,
3896             htmlDecode           : false,
3897             autoLoadKaTeX        : true,
3898             pageBreak            : true,
3899             atLink               : true,    // for @link
3900             emailLink            : true,    // for mail address auto link
3901             tex                  : false,
3902             taskList             : false,   // Github Flavored Markdown task lists
3903             emoji                : false,
3904             flowChart            : false,
3905             sequenceDiagram      : false,
3906             previewCodeHighlight : true
3907         };
3908         
3909         editormd.$marked  = marked;
3910
3911         var div           = $("#" + id);
3912         var settings      = div.settings = $.extend(true, defaults, options || {});
3913         var saveTo        = div.find("textarea");
3914         
3915         if (saveTo.length < 1)
3916         {
3917             div.append("<textarea></textarea>");
3918             saveTo        = div.find("textarea");
3919         }        
3920         
3921         var markdownDoc   = (settings.markdown === "") ? saveTo.val() : settings.markdown; 
3922         var markdownToC   = [];
3923
3924         var rendererOptions = {  
3925             toc                  : settings.toc,
3926             tocm                 : settings.tocm,
3927             tocStartLevel        : settings.tocStartLevel,
3928             taskList             : settings.taskList,
3929             emoji                : settings.emoji,
3930             tex                  : settings.tex,
3931             pageBreak            : settings.pageBreak,
3932             atLink               : settings.atLink,           // for @link
3933             emailLink            : settings.emailLink,        // for mail address auto link
3934             flowChart            : settings.flowChart,
3935             sequenceDiagram      : settings.sequenceDiagram,
3936             previewCodeHighlight : settings.previewCodeHighlight,
3937         };
3938
3939         var markedOptions = {
3940             renderer    : editormd.markedRenderer(markdownToC, rendererOptions),
3941             gfm         : settings.gfm,
3942             tables      : true,
3943             breaks      : true,
3944             pedantic    : false,
3945             sanitize    : (settings.htmlDecode) ? false : true, // 是否忽略HTML标签,即是否开启HTML标签解析,为了安全性,默认不开启
3946             smartLists  : true,
3947             smartypants : true
3948         };
3949         
3950         markdownDoc = new String(markdownDoc);
3951         
3952         var markdownParsed = marked(markdownDoc, markedOptions);
3953         
3954         markdownParsed = editormd.filterHTMLTags(markdownParsed, settings.htmlDecode);
3955         
3956         if (settings.markdownSourceCode) {
3957             saveTo.text(markdownDoc);
3958         } else {
3959             saveTo.remove();
3960         }
3961         
3962         div.addClass("markdown-body " + this.classPrefix + "html-preview").append(markdownParsed);
3963         
3964         var tocContainer = (settings.tocContainer !== "") ? $(settings.tocContainer) : div;
3965         
3966         if (settings.tocContainer !== "")
3967         {
3968             tocContainer.attr("previewContainer", false);
3969         }
3970          
3971         if (settings.toc) 
3972         {
3973             div.tocContainer = this.markdownToCRenderer(markdownToC, tocContainer, settings.tocDropdown, settings.tocStartLevel);
3974             
3975             if (settings.tocDropdown || div.find("." + this.classPrefix + "toc-menu").length > 0)
3976             {
3977                 this.tocDropdownMenu(div, settings.tocTitle);
3978             }
3979             
3980             if (settings.tocContainer !== "")
3981             {
3982                 div.find(".editormd-toc-menu, .editormd-markdown-toc").remove();
3983             }
3984         }
3985             
3986         if (settings.previewCodeHighlight) 
3987         {
3988             div.find("pre").addClass("prettyprint linenums");
3989             prettyPrint();
3990         }
3991         
3992         if (!editormd.isIE8) 
3993         {
3994             if (settings.flowChart) {
3995                 div.find(".flowchart").flowChart(); 
3996             }
3997
3998             if (settings.sequenceDiagram) {
3999                 div.find(".sequence-diagram").sequenceDiagram({theme: "simple"});
4000             }
4001         }
4002
4003         if (settings.tex)
4004         {
4005             var katexHandle = function() {
4006                 div.find("." + editormd.classNames.tex).each(function(){
4007                     var tex  = $(this);                    
4008                     katex.render(tex.html().replace(/&lt;/g, "<").replace(/&gt;/g, ">"), tex[0]);                    
4009                     tex.find(".katex").css("font-size", "1.6em");
4010                 });
4011             };
4012             
4013             if (settings.autoLoadKaTeX && !editormd.$katex && !editormd.kaTeXLoaded)
4014             {
4015                 this.loadKaTeX(function() {
4016                     editormd.$katex      = katex;
4017                     editormd.kaTeXLoaded = true;
4018                     katexHandle();
4019                 });
4020             }
4021             else
4022             {
4023                 katexHandle();
4024             }
4025         }
4026         
4027         div.getMarkdown = function() {            
4028             return saveTo.val();
4029         };
4030         
4031         return div;
4032     };
4033     
4034     // Editor.md themes, change toolbar themes etc.
4035     // added @1.5.0
4036     editormd.themes        = ["default", "dark"];
4037     
4038     // Preview area themes
4039     // added @1.5.0
4040     editormd.previewThemes = ["default", "dark"];
4041     
4042     // CodeMirror / editor area themes
4043     // @1.5.0 rename -> editorThemes, old version -> themes
4044     editormd.editorThemes = [
4045         "default", "3024-day", "3024-night",
4046         "ambiance", "ambiance-mobile",
4047         "base16-dark", "base16-light", "blackboard",
4048         "cobalt",
4049         "eclipse", "elegant", "erlang-dark",
4050         "lesser-dark",
4051         "mbo", "mdn-like", "midnight", "monokai",
4052         "neat", "neo", "night",
4053         "paraiso-dark", "paraiso-light", "pastel-on-dark",
4054         "rubyblue",
4055         "solarized",
4056         "the-matrix", "tomorrow-night-eighties", "twilight",
4057         "vibrant-ink",
4058         "xq-dark", "xq-light"
4059     ];
4060
4061     editormd.loadPlugins = {};
4062     
4063     editormd.loadFiles = {
4064         js     : [],
4065         css    : [],
4066         plugin : []
4067     };
4068     
4069     /**
4070      * 动态加载Editor.md插件,但不立即执行
4071      * Load editor.md plugins
4072      * 
4073      * @param {String}   fileName              插件文件路径
4074      * @param {Function} [callback=function()] 加载成功后执行的回调函数
4075      * @param {String}   [into="head"]         嵌入页面的位置
4076      */
4077     
4078     editormd.loadPlugin = function(fileName, callback, into) {
4079         callback   = callback || function() {};
4080         
4081         this.loadScript(fileName, function() {
4082             editormd.loadFiles.plugin.push(fileName);
4083             callback();
4084         }, into);
4085     };
4086     
4087     /**
4088      * 动态加载CSS文件的方法
4089      * Load css file method
4090      * 
4091      * @param {String}   fileName              CSS文件名
4092      * @param {Function} [callback=function()] 加载成功后执行的回调函数
4093      * @param {String}   [into="head"]         嵌入页面的位置
4094      */
4095     
4096     editormd.loadCSS   = function(fileName, callback, into) {
4097         into       = into     || "head";        
4098         callback   = callback || function() {};
4099         
4100         var css    = document.createElement("link");
4101         css.type   = "text/css";
4102         css.rel    = "stylesheet";
4103         css.onload = css.onreadystatechange = function() {
4104             editormd.loadFiles.css.push(fileName);
4105             callback();
4106         };
4107
4108         css.href   = fileName + ".css";
4109
4110         if(into === "head") {
4111             document.getElementsByTagName("head")[0].appendChild(css);
4112         } else {
4113             document.body.appendChild(css);
4114         }
4115     };
4116     
4117     editormd.isIE    = (navigator.appName == "Microsoft Internet Explorer");
4118     editormd.isIE8   = (editormd.isIE && navigator.appVersion.match(/8./i) == "8.");
4119
4120     /**
4121      * 动态加载JS文件的方法
4122      * Load javascript file method
4123      * 
4124      * @param {String}   fileName              JS文件名
4125      * @param {Function} [callback=function()] 加载成功后执行的回调函数
4126      * @param {String}   [into="head"]         嵌入页面的位置
4127      */
4128
4129     editormd.loadScript = function(fileName, callback, into) {
4130         
4131         into          = into     || "head";
4132         callback      = callback || function() {};
4133         
4134         var script    = null; 
4135         script        = document.createElement("script");
4136         script.id     = fileName.replace(/[\./]+/g, "-");
4137         script.type   = "text/javascript";        
4138         script.src    = fileName + ".js";
4139         
4140         if (editormd.isIE8) 
4141         {            
4142             script.onreadystatechange = function() {
4143                 if(script.readyState) 
4144                 {
4145                     if (script.readyState === "loaded" || script.readyState === "complete") 
4146                     {
4147                         script.onreadystatechange = null; 
4148                         editormd.loadFiles.js.push(fileName);
4149                         callback();
4150                     }
4151                 } 
4152             };
4153         }
4154         else
4155         {
4156             script.onload = function() {
4157                 editormd.loadFiles.js.push(fileName);
4158                 callback();
4159             };
4160         }
4161
4162         if (into === "head") {
4163             document.getElementsByTagName("head")[0].appendChild(script);
4164         } else {
4165             document.body.appendChild(script);
4166         }
4167     };
4168     
4169     // 使用国外的CDN,加载速度有时会很慢,或者自定义URL
4170     // You can custom KaTeX load url.
4171     editormd.katexURL  = {
4172         css : "//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.3.0/katex.min",
4173         js  : "//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.3.0/katex.min"
4174     };
4175     
4176     editormd.kaTeXLoaded = false;
4177     
4178     /**
4179      * 加载KaTeX文件
4180      * load KaTeX files
4181      * 
4182      * @param {Function} [callback=function()]  加载成功后执行的回调函数
4183      */
4184     
4185     editormd.loadKaTeX = function (callback) {
4186         editormd.loadCSS(editormd.katexURL.css, function(){
4187             editormd.loadScript(editormd.katexURL.js, callback || function(){});
4188         });
4189     };
4190         
4191     /**
4192      * 锁屏
4193      * lock screen
4194      * 
4195      * @param   {Boolean}   lock   Boolean 布尔值,是否锁屏
4196      * @returns {void}
4197      */
4198     
4199     editormd.lockScreen = function(lock) {
4200         $("html,body").css("overflow", (lock) ? "hidden" : "");
4201     };
4202         
4203     /**
4204      * 动态创建对话框
4205      * Creating custom dialogs
4206      * 
4207      * @param   {Object} options 配置项键值对 Key/Value
4208      * @returns {dialog} 返回创建的dialog的jQuery实例对象
4209      */
4210
4211     editormd.createDialog = function(options) {
4212         var defaults = {
4213             name : "",
4214             width : 420,
4215             height: 240,
4216             title : "",
4217             drag  : true,
4218             closed : true,
4219             content : "",
4220             mask : true,
4221             maskStyle : {
4222                 backgroundColor : "#fff",
4223                 opacity : 0.1
4224             },
4225             lockScreen : true,
4226             footer : true,
4227             buttons : false
4228         };
4229
4230         options          = $.extend(true, defaults, options);
4231         
4232         var $this        = this;
4233         var editor       = this.editor;
4234         var classPrefix  = editormd.classPrefix;
4235         var guid         = (new Date()).getTime();
4236         var dialogName   = ( (options.name === "") ? classPrefix + "dialog-" + guid : options.name);
4237         var mouseOrTouch = editormd.mouseOrTouch;
4238
4239         var html         = "<div class=\"" + classPrefix + "dialog " + dialogName + "\">";
4240
4241         if (options.title !== "")
4242         {
4243             html += "<div class=\"" + classPrefix + "dialog-header\"" + ( (options.drag) ? " style=\"cursor: move;\"" : "" ) + ">";
4244             html += "<strong class=\"" + classPrefix + "dialog-title\">" + options.title + "</strong>";
4245             html += "</div>";
4246         }
4247
4248         if (options.closed)
4249         {
4250             html += "<a href=\"javascript:;\" class=\"fa fa-close " + classPrefix + "dialog-close\"></a>";
4251         }
4252
4253         html += "<div class=\"" + classPrefix + "dialog-container\">" + options.content;                    
4254
4255         if (options.footer || typeof options.footer === "string") 
4256         {
4257             html += "<div class=\"" + classPrefix + "dialog-footer\">" + ( (typeof options.footer === "boolean") ? "" : options.footer) + "</div>";
4258         }
4259
4260         html += "</div>";
4261
4262         html += "<div class=\"" + classPrefix + "dialog-mask " + classPrefix + "dialog-mask-bg\"></div>";
4263         html += "<div class=\"" + classPrefix + "dialog-mask " + classPrefix + "dialog-mask-con\"></div>";
4264         html += "</div>";
4265
4266         editor.append(html);
4267
4268         var dialog = editor.find("." + dialogName);
4269
4270         dialog.lockScreen = function(lock) {
4271             if (options.lockScreen)
4272             {                
4273                 $("html,body").css("overflow", (lock) ? "hidden" : "");
4274                 $this.resize();
4275             }
4276
4277             return dialog;
4278         };
4279
4280         dialog.showMask = function() {
4281             if (options.mask)
4282             {
4283                 editor.find("." + classPrefix + "mask").css(options.maskStyle).css("z-index", editormd.dialogZindex - 1).show();
4284             }
4285             return dialog;
4286         };
4287
4288         dialog.hideMask = function() {
4289             if (options.mask)
4290             {
4291                 editor.find("." + classPrefix + "mask").hide();
4292             }
4293
4294             return dialog;
4295         };
4296
4297         dialog.loading = function(show) {                        
4298             var loading = dialog.find("." + classPrefix + "dialog-mask");
4299             loading[(show) ? "show" : "hide"]();
4300
4301             return dialog;
4302         };
4303
4304         dialog.lockScreen(true).showMask();
4305
4306         dialog.show().css({
4307             zIndex : editormd.dialogZindex,
4308             border : (editormd.isIE8) ? "1px solid #ddd" : "",
4309             width  : (typeof options.width  === "number") ? options.width + "px"  : options.width,
4310             height : (typeof options.height === "number") ? options.height + "px" : options.height
4311         });
4312
4313         var dialogPosition = function(){
4314             dialog.css({
4315                 top    : ($(window).height() - dialog.height()) / 2 + "px",
4316                 left   : ($(window).width() - dialog.width()) / 2 + "px"
4317             });
4318         };
4319
4320         dialogPosition();
4321
4322         $(window).resize(dialogPosition);
4323
4324         dialog.children("." + classPrefix + "dialog-close").bind(mouseOrTouch("click", "touchend"), function() {
4325             dialog.hide().lockScreen(false).hideMask();
4326         });
4327
4328         if (typeof options.buttons === "object")
4329         {
4330             var footer = dialog.footer = dialog.find("." + classPrefix + "dialog-footer");
4331
4332             for (var key in options.buttons)
4333             {
4334                 var btn = options.buttons[key];
4335                 var btnClassName = classPrefix + key + "-btn";
4336
4337                 footer.append("<button class=\"" + classPrefix + "btn " + btnClassName + "\">" + btn[0] + "</button>");
4338                 btn[1] = $.proxy(btn[1], dialog);
4339                 footer.children("." + btnClassName).bind(mouseOrTouch("click", "touchend"), btn[1]);
4340             }
4341         }
4342
4343         if (options.title !== "" && options.drag)
4344         {                        
4345             var posX, posY;
4346             var dialogHeader = dialog.children("." + classPrefix + "dialog-header");
4347
4348             if (!options.mask) {
4349                 dialogHeader.bind(mouseOrTouch("click", "touchend"), function(){
4350                     editormd.dialogZindex += 2;
4351                     dialog.css("z-index", editormd.dialogZindex);
4352                 });
4353             }
4354
4355             dialogHeader.mousedown(function(e) {
4356                 e = e || window.event;  //IE
4357                 posX = e.clientX - parseInt(dialog[0].style.left);
4358                 posY = e.clientY - parseInt(dialog[0].style.top);
4359
4360                 document.onmousemove = moveAction;                   
4361             });
4362
4363             var userCanSelect = function (obj) {
4364                 obj.removeClass(classPrefix + "user-unselect").off("selectstart");
4365             };
4366
4367             var userUnselect = function (obj) {
4368                 obj.addClass(classPrefix + "user-unselect").on("selectstart", function(event) { // selectstart for IE                        
4369                     return false;
4370                 });
4371             };
4372
4373             var moveAction = function (e) {
4374                 e = e || window.event;  //IE
4375
4376                 var left, top, nowLeft = parseInt(dialog[0].style.left), nowTop = parseInt(dialog[0].style.top);
4377
4378                 if( nowLeft >= 0 ) {
4379                     if( nowLeft + dialog.width() <= $(window).width()) {
4380                         left = e.clientX - posX;
4381                     } else {    
4382                         left = $(window).width() - dialog.width();
4383                         document.onmousemove = null;
4384                     }
4385                 } else {
4386                     left = 0;
4387                     document.onmousemove = null;
4388                 }
4389
4390                 if( nowTop >= 0 ) {
4391                     top = e.clientY - posY;
4392                 } else {
4393                     top = 0;
4394                     document.onmousemove = null;
4395                 }
4396
4397
4398                 document.onselectstart = function() {
4399                     return false;
4400                 };
4401
4402                 userUnselect($("body"));
4403                 userUnselect(dialog);
4404                 dialog[0].style.left = left + "px";
4405                 dialog[0].style.top  = top + "px";
4406             };
4407
4408             document.onmouseup = function() {                            
4409                 userCanSelect($("body"));
4410                 userCanSelect(dialog);
4411
4412                 document.onselectstart = null;         
4413                 document.onmousemove = null;
4414             };
4415
4416             dialogHeader.touchDraggable = function() {
4417                 var offset = null;
4418                 var start  = function(e) {
4419                     var orig = e.originalEvent; 
4420                     var pos  = $(this).parent().position();
4421
4422                     offset = {
4423                         x : orig.changedTouches[0].pageX - pos.left,
4424                         y : orig.changedTouches[0].pageY - pos.top
4425                     };
4426                 };
4427
4428                 var move = function(e) {
4429                     e.preventDefault();
4430                     var orig = e.originalEvent;
4431
4432                     $(this).parent().css({
4433                         top  : orig.changedTouches[0].pageY - offset.y,
4434                         left : orig.changedTouches[0].pageX - offset.x
4435                     });
4436                 };
4437
4438                 this.bind("touchstart", start).bind("touchmove", move);
4439             };
4440
4441             dialogHeader.touchDraggable();
4442         }
4443
4444         editormd.dialogZindex += 2;
4445
4446         return dialog;
4447     };
4448     
4449     /**
4450      * 鼠标和触摸事件的判断/选择方法
4451      * MouseEvent or TouchEvent type switch
4452      * 
4453      * @param   {String} [mouseEventType="click"]    供选择的鼠标事件
4454      * @param   {String} [touchEventType="touchend"] 供选择的触摸事件
4455      * @returns {String} EventType                   返回事件类型名称
4456      */
4457     
4458     editormd.mouseOrTouch = function(mouseEventType, touchEventType) {
4459         mouseEventType = mouseEventType || "click";
4460         touchEventType = touchEventType || "touchend";
4461         
4462         var eventType  = mouseEventType;
4463
4464         try {
4465             document.createEvent("TouchEvent");
4466             eventType = touchEventType;
4467         } catch(e) {}
4468
4469         return eventType;
4470     };
4471     
4472     /**
4473      * 日期时间的格式化方法
4474      * Datetime format method
4475      * 
4476      * @param   {String}   [format=""]  日期时间的格式,类似PHP的格式
4477      * @returns {String}   datefmt      返回格式化后的日期时间字符串
4478      */
4479     
4480     editormd.dateFormat = function(format) {                
4481         format      = format || "";
4482
4483         var addZero = function(d) {
4484             return (d < 10) ? "0" + d : d;
4485         };
4486
4487         var date    = new Date(); 
4488         var year    = date.getFullYear();
4489         var year2   = year.toString().slice(2, 4);
4490         var month   = addZero(date.getMonth() + 1);
4491         var day     = addZero(date.getDate());
4492         var weekDay = date.getDay();
4493         var hour    = addZero(date.getHours());
4494         var min     = addZero(date.getMinutes());
4495         var second  = addZero(date.getSeconds());
4496         var ms      = addZero(date.getMilliseconds()); 
4497         var datefmt = "";
4498
4499         var ymd     = year2 + "-" + month + "-" + day;
4500         var fymd    = year  + "-" + month + "-" + day;
4501         var hms     = hour  + ":" + min   + ":" + second;
4502
4503         switch (format) 
4504         {
4505             case "UNIX Time" :
4506                     datefmt = date.getTime();
4507                 break;
4508
4509             case "UTC" :
4510                     datefmt = date.toUTCString();
4511                 break;    
4512
4513             case "yy" :
4514                     datefmt = year2;
4515                 break;    
4516
4517             case "year" :
4518             case "yyyy" :
4519                     datefmt = year;
4520                 break;
4521
4522             case "month" :
4523             case "mm" :
4524                     datefmt = month;
4525                 break;                        
4526
4527             case "cn-week-day" :
4528             case "cn-wd" :
4529                     var cnWeekDays = ["日", "一", "二", "三", "四", "五", "六"];
4530                     datefmt = "星期" + cnWeekDays[weekDay];
4531                 break;
4532
4533             case "week-day" :
4534             case "wd" :
4535                     var weekDays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
4536                     datefmt = weekDays[weekDay];
4537                 break;
4538
4539             case "day" :
4540             case "dd" :
4541                     datefmt = day;
4542                 break;
4543
4544             case "hour" :
4545             case "hh" :
4546                     datefmt = hour;
4547                 break;
4548
4549             case "min" :
4550             case "ii" :
4551                     datefmt = min;
4552                 break;
4553
4554             case "second" :
4555             case "ss" :
4556                     datefmt = second;
4557                 break;
4558
4559             case "ms" :
4560                     datefmt = ms;
4561                 break;
4562
4563             case "yy-mm-dd" :
4564                     datefmt = ymd;
4565                 break;
4566
4567             case "yyyy-mm-dd" :
4568                     datefmt = fymd;
4569                 break;
4570
4571             case "yyyy-mm-dd h:i:s ms" :
4572             case "full + ms" : 
4573                     datefmt = fymd + " " + hms + " " + ms;
4574                 break;
4575
4576             case "full" :
4577             case "yyyy-mm-dd h:i:s" :
4578                 default:
4579                     datefmt = fymd + " " + hms;
4580                 break;
4581         }
4582
4583         return datefmt;
4584     };
4585
4586     return editormd;
4587
4588 }));