/* * Editor.md * @file editormd.js * @version v1.1.9 * @description A simple online markdown editor. * @license MIT License * @author Pandao * {@link https://github.com/pandao/editor.md} * @updateTime 2015-03-04 */ /** * @fileOverview Editor.md * @author pandao * @version 1.1.9 */ ;(function(factory) { "use strict"; // CommonJS/Node.js if (typeof require === "function" && typeof exports === "object" && typeof module === "object") { module.exports = factory(); } else if (typeof define === "function") // AMD/CMD/Sea.js { define(["jquery"], factory); } else { window.editormd = factory(); } }(function() { /** * editormd * * @param {String} id 编辑器的ID * @param {Object} options 配置选项 Key/Value * @returns {Object} editormd 返回editormd对象 */ "use strict"; var $ = (typeof (jQuery) !== "undefined") ? jQuery : Zepto; if (typeof ($) === "undefined") { return ; } var editormd = function (id, options) { return new editormd.fn.init(id, options); }; editormd.title = editormd.$name = "Editor.md"; editormd.version = "1.1.9"; editormd.homePage = "https://pandao.github.io/editor.md/"; editormd.classPrefix = "editormd-"; editormd.defaults = { mode : "gfm", //gfm or markdown markdown : "", width : "100%", height : "100%", path : "./lib/", watch : true, placeholder : "now coding markdown...", readOnly : false, lineNumbers : true, matchWordHighlight : true, // options: true, false, "onselected" styleActiveLine : true, // Highlight the current line dialogLockScreen : true, dialogShowMask : true, dialogDraggable : true, dialogMaskBgColor : "#fff", dialogMaskOpacity : 0.1, onload : function() {}, onchange : function() {}, onfullscreen : function() {}, onfullscreenExit : function() {}, imageUpload : false, imageFormats : ["jpg", "jpeg", "gif", "png", "bmp", "webp"], imageUploadURL : "", crossDomainUpload : false, uploadCallbackURL : "", saveHTMLToTextarea : false, toc : true, tocStartLevel : 1, // Said from H1 to create ToC fontSize : "13px", htmlDecode : false, // Open the HTML tag identification tex : false, flowChart : false, // flowChart.js only support IE9+ sequenceDiagram : false, // sequenceDiagram.js only support IE9+ previewCodeHighlight : true, inRequirejs : false, toolbar : true, toolbarIcons : function() { return [ "undo", "redo", "|", "bold", "del", "italic", "quote", "|", "h1", "h2", "h3", "h4", "h5", "h6", "|", "list-ul", "list-ol", "hr", "|", "link", "anchor", "image", "code", "code-block-tab", "code-block", "datetime", "|", "watch", "preview", "fullscreen", "clear", "|", "info" ] }, toolbarTitles : {}, toolbarHandlers : {}, toolbarIconsClass : { undo : "fa-undo", redo : "fa-repeat", bold : "fa-bold", del : "fa-strikethrough", italic : "fa-italic", quote : "fa-quote-left", h1 : editormd.classPrefix + "bold", h2 : editormd.classPrefix + "bold", h3 : editormd.classPrefix + "bold", h4 : editormd.classPrefix + "bold", h5 : editormd.classPrefix + "bold", h6 : editormd.classPrefix + "bold", "list-ul" : "fa-list-ul", "list-ol" : "fa-list-ol", hr : "fa-minus", link : "fa-link", anchor : "fa-anchor", image : "fa-picture-o", code : "fa-code", "code-block-tab" : "fa-file-code-o", "code-block" : "fa-file-code-o", datetime : "fa-clock-o", watch : "fa-eye-slash", unwatch : "fa-eye", preview : "fa-search", fullscreen : "fa-arrows-alt", clear : "fa-eraser", info : "fa-info-circle" }, toolbarIconTexts : {}, lang : { name : "zh-cn", description : "开源在线Markdown编辑器
A simple markdown doucment online editor.", toolbar : { undo : "撤销(Ctrl+Z)", redo : "重做(Ctrl+Y)", bold : "粗体", del : "删除线", italic : "斜体", quote : "引用", h1 : "标题1", h2 : "标题2", h3 : "标题3", h4 : "标题4", h5 : "标题5", h6 : "标题6", "list-ul" : "无序列表", "list-ol" : "有序列表", hr : "横线", link : "链接", anchor : "锚点", image : "图片", code : "行内代码", "code-block-tab" : "预格式文本 / 代码块(缩进风格)", "code-block" : "代码块(多语言风格)", datetime : "日期时间", watch : "关闭实时预览", unwatch : "开启实时预览", preview : "全窗口预览HTML(可按ESC还原)", fullscreen : "全屏(按ESC还原)", clear : "清空", info : "关于" + editormd.title }, buttons : { enter : "确定", cancel : "取消" }, dialog : { link : { title : "添加链接", url : "链接地址", urlTitle : "链接标题", urlEmpty : "错误:请填写链接地址。", titleEmpty : "错误:请填写链接标题。" }, anchor : { title : "添加锚点链接", name : "锚点名称", url : "链接地址", urlTitle : "链接标题", nameEmpty: "错误:锚点名称不能为空。", titleEmpty : "错误:请填写锚点链接标题。", urlEmpty : "错误:请填写锚点链接地址。" }, image : { title : "添加图片", url : "图片地址", link : "图片链接", alt : "图片描述", uploadButton : "本地上传", imageURLEmpty : "错误:图片地址不能为空。", uploadFileEmpty : "错误:上传的图片不能为空。", formatNotAllowed : "错误:只允许上传图片文件,允许上传的图片文件格式有:" }, tabCodeBlock : { title : "添加预格式文本或代码块", codeEmptyAlert : "错误:请填写预格式文本或代码的内容。" }, codeBlock : { title : "添加代码块", selectLabel : "代码语言:", selectDefaultText : "请选择代码语言", otherLanguage : "其他语言", unselectedLanguageAlert : "错误:请选择代码所属的语言类型。", codeEmptyAlert : "错误:请填写代码内容。" } } } }; editormd.classNames = { tex : editormd.classPrefix + "tex" }; editormd.dialogZindex = 99999; editormd.$katex = null; editormd.$marked = null; editormd.$CodeMirror = null; editormd.$prettyPrint = null; editormd.prototype = editormd.fn = { state : { watching : false, loaded : false, preview : false, fullscreen : false }, /** * 构造函数/实例初始化 * @param {String} id 编辑器的ID * @param {Object} [options={}] 配置选项 Key/Value * @returns {editormd} 返回editormd的实例对象 */ init : function (id, options) { options = options || {}; var _this = this; var classPrefix = this.classPrefix = editormd.classPrefix; var editor = this.editor = $("#" + id); var settings = this.settings = $.extend(true, editormd.defaults, options); this.id = id; this.lang = settings.lang; var classNames = this.classNames = { textarea : { html : this.classPrefix + "html-textarea", markdown : this.classPrefix + "markdown-textarea" } }; this.state.watching = (settings.watch) ? true : false; if (!editor.hasClass("editormd")) { editor.addClass("editormd"); } editor.css({ width : (typeof settings.width === "number") ? settings.width + "px" : settings.width, height : (typeof settings.height === "number") ? settings.height + "px" : settings.height }); var markdownTextarea = this.markdownTextarea = editor.children("textarea"); if (markdownTextarea.length < 1) { editor.append(""); markdownTextarea = this.markdownTextarea = editor.children("textarea"); } markdownTextarea.addClass(classNames.textarea.markdown).attr("name", id + "-markdown-doc").attr("placeholder", settings.placeholder); var appendElements = [ (!settings.readOnly) ? "" : "", ( (settings.saveHTMLToTextarea) ? "" : "" ), "
", "
", "
" ].join("\n"); editor.append(appendElements).addClass(classPrefix + "vertical"); if (settings.markdown !== "") { markdownTextarea.val(settings.markdown); } this.htmlTextarea = editor.find("." + classNames.textarea.html); this.preview = editor.find("." + classPrefix + "preview"); this.previewContainer = this.preview.children("." + classPrefix + "preview-container"); this.toolbarIconHandlers = {}; if (!settings.inRequirejs) { this.loadQueues(); } else { _this.setCodeMirror(); _this.setToolbar(); _this.setMarked().loadedDisplay(); } return this; }, /** * 所需组件加载队列 * @returns {editormd} 返回editormd的实例对象 */ loadQueues : function() { var _this = this; var settings = this.settings; var loadPath = settings.path; var loadFlowChartOrSequenceDiagram = function() { if (editormd.isIE8) { _this.setMarked().loadedDisplay(); return ; } if (settings.flowChart || settings.sequenceDiagram) { editormd.loadScript(loadPath + "raphael.min", function() { editormd.loadScript(loadPath + "underscore.min", function() { if (!settings.flowChart && settings.sequenceDiagram) { editormd.loadScript(loadPath + "sequence-diagram.min", function() { _this.setMarked().loadedDisplay(); }); } else if (settings.flowChart && !settings.sequenceDiagram) { editormd.loadScript(loadPath + "flowchart.min", function() { editormd.loadScript(loadPath + "jquery.flowchart.min", function() { _this.setMarked().loadedDisplay(); }); }); } else if (settings.flowChart && settings.sequenceDiagram) { editormd.loadScript(loadPath + "flowchart.min", function() { editormd.loadScript(loadPath + "jquery.flowchart.min", function() { editormd.loadScript(loadPath + "sequence-diagram.min", function() { _this.setMarked().loadedDisplay(); }); }); }); } }); }); } else { _this.setMarked().loadedDisplay(); } }; editormd.loadCSS(loadPath + "codemirror/codemirror.min"); editormd.loadScript(loadPath + "codemirror/codemirror.min", function() { editormd.$CodeMirror = CodeMirror; editormd.loadScript(loadPath + "codemirror/modes.min", function() { editormd.loadScript(loadPath + "codemirror/addons.min", function() { _this.setCodeMirror(); _this.setToolbar(); editormd.loadScript(loadPath + "marked.min", function() { editormd.$marked = marked; if (settings.previewCodeHighlight) { editormd.loadScript(loadPath + "prettify.min", function() { loadFlowChartOrSequenceDiagram(); }); } else { loadFlowChartOrSequenceDiagram(); } }); }); }); }); return this; }, /** * 配置和初始化CodeMirror组件 * @returns {editormd} 返回editormd的实例对象 */ setCodeMirror : function() { var settings = this.settings; var editor = this.editor; var codeMirrorConfig = { mode : settings.mode, theme : "default", tabSize : 4, dragDrop : false, autofocus : true, readOnly : (settings.readOnly) ? "nocursor" : false, indentUnit : 4, lineNumbers : settings.lineNumbers, lineWrapping : true, matchBrackets : true, indentWithTabs : true, styleActiveLine : settings.styleActiveLine, styleSelectedText : true, autoCloseBrackets : true, showTrailingSpace : true, highlightSelectionMatches : ( (!settings.matchWordHighlight) ? false : { showToken: (settings.matchWordHighlight == "onselected") ? false : /\w/ }) }; this.codeEditor = editormd.$CodeMirror.fromTextArea(this.markdownTextarea[0], codeMirrorConfig); this.codeMirror = editor.find(".CodeMirror"); this.codeMirror.css({ fontSize : settings.fontSize, width : (!settings.watch) ? "100%" : "50%" }); return this; }, /** * 显示工具栏 * @returns {editormd} 返回editormd的实例对象 */ showToolbar : function() { var settings = this.settings; if(settings.readOnly) return ; settings.toolbar = true; this.toolbar.show(); this.resize(); return this; }, /** * 隐藏工具栏 * @returns {editormd} 返回editormd的实例对象 */ hideToolbar : function() { var settings = this.settings; settings.toolbar = false; this.toolbar.hide(); this.resize(); return this; }, /** * 配置和初始化工具栏 * @returns {editormd} 返回editormd的实例对象 */ setToolbar : function() { var settings = this.settings; if(settings.readOnly) return ; var editor = this.editor; var preview = this.preview; var classPrefix = this.classPrefix; editor.append('
'); var toolbar = this.toolbar = editor.find("." + classPrefix + "toolbar"); if (!settings.toolbar) { toolbar.hide(); return ; } else { toolbar.show(); } var icons = settings.toolbarIcons(); var toolbarMenu = toolbar.find("." + this.classPrefix + "menu"), menu = ""; for (var i = 0, len = icons.length; i < len; i++) { var name = icons[i]; if (name !== "|") { var isHeader = (/h(\d)/.test(name)); var index = name; if (name === "watch" && !settings.watch) { index = "unwatch"; } var title = settings.lang.toolbar[index]; var iconTexts = settings.toolbarIconTexts[index]; var iconClass = settings.toolbarIconsClass[index]; title = (typeof title === "undefined") ? "" : title; iconTexts = (typeof iconTexts === "undefined") ? "" : iconTexts; iconClass = (typeof iconClass === "undefined") ? "" : iconClass; menu += "
  • " + ""+((isHeader) ? name : ( (iconClass === "") ? iconTexts : "") ) + "
  • "; } else { menu += "
  • |
  • "; } } toolbarMenu.html(menu); this.setToolbarHandler(); return this; }, /** * 工具栏图标事件处理对象序列 * @param {Object} cm CodeMirror的实例对象 * @param {String} name 要获取的事件处理器名称 * @returns {Object} 返回处理对象序列 */ getToolbarHandles : function(name) { var _this = this; var settings = this.settings; var lang = settings.lang; var editor = this.editor; var classPrefix = this.classPrefix; var dialogLockScreen = function() { if (settings.dialogLockScreen) { $("html,body").css("overflow", "hidden"); } }; var dialogShowMask = function(dialog) { dialog.css({ top : ($(window).height() - dialog.height()) / 2 + "px", left : ($(window).width() - dialog.width()) / 2 + "px" }); if (settings.dialogShowMask) { editor.find("." + classPrefix + "mask").css("z-index", parseInt(dialog.css("z-index")) - 1).show(); } }; var toolbarHandlers = this.toolbarHandlers = { undo : function(cm) { cm.undo(); }, redo : function(cm) { cm.redo(); }, bold : function(cm) { var cursor = cm.getCursor(); var selection = cm.getSelection(); cm.replaceSelection("**" + selection + "**"); if(selection === "") { cm.setCursor(cursor.line, cursor.ch + 2); } }, del : function(cm) { var cursor = cm.getCursor(); var selection = cm.getSelection(); cm.replaceSelection("~~" + selection + "~~"); if(selection === "") { cm.setCursor(cursor.line, cursor.ch + 2); } }, italic : function(cm) { var cursor = cm.getCursor(); var selection = cm.getSelection(); cm.replaceSelection("*" + selection + "*"); if(selection === "") { cm.setCursor(cursor.line, cursor.ch + 1); } }, quote : function(cm) { var cursor = cm.getCursor(); var selection = cm.getSelection(); cm.replaceSelection("> " + selection); cm.setCursor(cursor.line, (selection === "") ? cursor.ch + 2 : cursor.ch + selection.length + 2); }, h1 : function(cm) { var selection = cm.getSelection(); cm.replaceSelection("# " + selection); }, h2 : function(cm) { var selection = cm.getSelection(); cm.replaceSelection("## " + selection); }, h3 : function(cm) { var selection = cm.getSelection(); cm.replaceSelection("### " + selection); }, h4 : function(cm) { var selection = cm.getSelection(); cm.replaceSelection("#### " + selection); }, h5 : function(cm) { var selection = cm.getSelection(); cm.replaceSelection("##### " + selection); }, h6 : function(cm) { var selection = cm.getSelection(); cm.replaceSelection("###### " + selection); }, "list-ul" : function(cm) { var cursor = cm.getCursor(); var selection = cm.getSelection(); if (selection === "") { cm.replaceSelection("- " + selection); } else { var selectionText = selection.split("\n"); for (var i = 0, len = selectionText.length; i < len; i++) { selectionText[i] = (selectionText[i] === "") ? "" : "- " + selectionText[i]; } cm.replaceSelection(selectionText.join("\n")); } }, "list-ol" : function(cm) { var cursor = cm.getCursor(); var selection = cm.getSelection(); if(selection === "") { cm.replaceSelection("1. " + selection); } else { var selectionText = selection.split("\n"); for (var i = 0, len = selectionText.length; i < len; i++) { selectionText[i] = (selectionText[i] === "") ? "" : (i+1) + ". " + selectionText[i]; } cm.replaceSelection(selectionText.join("\n")); } }, hr : function(cm) { var cursor = cm.getCursor(); var selection = cm.getSelection(); cm.replaceSelection("------------"); }, link : function(cm) { var selection = cm.getSelection(); var linkLang = lang.dialog.link; var linkDialogName = classPrefix + "link-dialog", linkDialog; if (editor.find("." + linkDialogName).length > 0) { linkDialog = editor.find("." + linkDialogName); linkDialog.find("[data-url]").val("http://"); linkDialog.find("[data-title]").val(selection); dialogShowMask(linkDialog); dialogLockScreen(); linkDialog.show(); } else { var linkDialogHTML = "
    " + "" + "" + "
    " + "" + "" + "
    " + "
    "; linkDialog = _this.createDialog({ title : linkLang.title, width : 380, height : 210, content : linkDialogHTML, mask : settings.dialogShowMask, drag : settings.dialogDraggable, lockScreen : settings.dialogLockScreen, maskStyle : { opacity : settings.dialogMaskOpacity, backgroundColor : settings.dialogMaskBgColor }, buttons : { enter : [lang.buttons.enter, function() { var url = this.find("[data-url]").val(); var title = this.find("[data-title]").val(); if (url === "http://" || url === "") { alert(linkLang.urlEmpty); return false; } if (title === "") { alert(linkLang.titleEmpty); return false; } cm.replaceSelection("[" + title + "](" + url + " \""+title+"\")"); this.hide().lockScreen(false).hideMask(); return false; }], cancel : [lang.buttons.cancel, function() { this.hide().lockScreen(false).hideMask(); return false; }] } }); } }, anchor : function(cm) { var cursor = cm.getCursor(); var selection = cm.getSelection(); var anchorLang = lang.dialog.anchor; var anchorDialogName = classPrefix + "anchor-dialog", anchorDialog; if (editor.find("." + anchorDialogName).length > 0) { anchorDialog = editor.find("." + anchorDialogName); anchorDialog.find("[data-name]").val(""); anchorDialog.find("[data-url]").val("http://"); anchorDialog.find("[data-title]").val(selection); dialogShowMask(anchorDialog); dialogLockScreen(); anchorDialog.show(); } else { var anchorDialogHTML = "
    " + "" + "" + "
    " + "" + "" + "
    " + "" + "" + "
    " + "
    "; anchorDialog = _this.createDialog({ name : anchorDialogName, title : anchorLang.title, width : 380, height : 250, content : anchorDialogHTML, mask : settings.dialogShowMask, drag : settings.dialogDraggable, lockScreen : settings.dialogLockScreen, maskStyle : { opacity : settings.dialogMaskOpacity, backgroundColor : settings.dialogMaskBgColor }, buttons : { enter : [lang.buttons.enter, function() { var name = this.find("[data-name]").val(); var url = this.find("[data-url]").val(); var title = this.find("[data-title]").val(); if (name === "") { alert(anchorLang.nameEmpty); return false; } if (url === "http://" || url === "") { alert(anchorLang.urlEmpty); return false; } if (title === "") { alert(anchorLang.titleEmpty); return false; } cm.replaceSelection("[" + title + "][" + name + "]\n[" + name + "]: " + url + ""); if (selection === "") { cm.setCursor(cursor.line, cursor.ch + 1); } this.hide().lockScreen(false).hideMask(); return false; }], cancel : [lang.buttons.cancel, function() { this.hide().lockScreen(false).hideMask(); return false; }] } }); } }, image : function(cm) { var cursor = cm.getCursor(); var selection = cm.getSelection(); var imageLang = lang.dialog.image; var iframeName = classPrefix + "image-iframe"; var imageDialogName = classPrefix + "image-dialog", imageDialog; if (editor.find("." + imageDialogName).length > 0) { imageDialog = editor.find("." + imageDialogName); imageDialog.find("[type=\"text\"]").val(""); imageDialog.find("[type=\"file\"]").val(""); imageDialog.find("[data-link]").val("http://"); dialogShowMask(imageDialog); dialogLockScreen(); imageDialog.show(); } else { var guid = (new Date).getTime(); var action = settings.imageUploadURL + "?guid=" + guid; if (settings.crossDomainUpload) { action += "&callback=" + settings.uploadCallbackURL + "&dialog_id=editormd-image-dialog-" + guid; } var imageDialogHTML = ( (settings.imageUpload) ? "
    " : "
    " ) + ( (settings.imageUpload) ? "" : "" ) + "" + "" + (function(){ return (settings.imageUpload) ? "
    " + "" + "" + "
    " : ""; })() + "
    " + "" + "" + "
    " + "" + "" + "
    " + ( (settings.imageUpload) ? "" : "
    "); //var imageFooterHTML = ""; imageDialog = _this.createDialog({ title : imageLang.title, width : (settings.imageUpload) ? 465 : 380, height : 250, name : imageDialogName, content : imageDialogHTML, mask : settings.dialogShowMask, drag : settings.dialogDraggable, lockScreen : settings.dialogLockScreen, maskStyle : { opacity : settings.dialogMaskOpacity, backgroundColor : settings.dialogMaskBgColor }, buttons : { enter : [lang.buttons.enter, function() { var url = this.find("[data-url]").val(); var alt = this.find("[data-alt]").val(); var link = this.find("[data-link]").val(); if (url === "") { alert(imageLang.imageURLEmpty); return false; } if (link === "" || link === "http://") { cm.replaceSelection("![" + alt + "](" + url + " \"" + alt + "\")"); } else { cm.replaceSelection("[![" + alt + "](" + url + " \"" + alt + "\")](" + link + " \"" + alt + "\")"); } if (alt === "") { cm.setCursor(cursor.line, cursor.ch + 2); } this.hide().lockScreen(false).hideMask(); return false; }], cancel : [lang.buttons.cancel, function() { this.hide().lockScreen(false).hideMask(); return false; }] } }); imageDialog.attr("id", classPrefix + "image-dialog-" + guid); } var fileInput = imageDialog.find("[name=\"" + classPrefix + "image-file\"]"); fileInput.bind("change", function() { var fileName = fileInput.val(); var isImage = new RegExp("(\\.(" + settings.imageFormats.join("|") + "))$"); // /(\.(webp|jpg|jpeg|gif|bmp|png))$/ if (fileName === "") { alert(imageLang.uploadFileEmpty); } else if (!isImage.test(fileName)) { alert(imageLang.formatNotAllowed + settings.imageFormats.join(", ")); } else { imageDialog.loading(true); var submitHandler = function() { var uploadIframe = document.getElementById(iframeName); uploadIframe.onload = function() { imageDialog.loading(false); var json = uploadIframe.contentWindow.document.body.innerHTML; json = (typeof JSON.parse !== "undefined") ? JSON.parse(json) : eval("(" + json + ")"); if (json.success === 1) { imageDialog.find("[data-url]").val(json.url); } else { alert(json.message); } return false; }; }; imageDialog.find("[type=\"submit\"]").bind(editormd.mouseOrTouch("click", "touchend"), submitHandler).trigger("click"); } return false; }); }, code : function(cm) { var cursor = cm.getCursor(); var selection = cm.getSelection(); cm.replaceSelection("`" + selection + "`"); if (selection === "") { cm.setCursor(cursor.line, cursor.ch + 1); } }, "code-block-tab" : function(cm) { var cursor = cm.getCursor(); var selection = cm.getSelection(); var tabCodeBlockDialogName = classPrefix + "dialog-tab-code-block", tabCodeBlockDialog; if (editor.find("." + tabCodeBlockDialogName).length > 0) { tabCodeBlockDialog = editor.find("." + tabCodeBlockDialogName); tabCodeBlockDialog.find("textarea").val(selection); dialogShowMask(tabCodeBlockDialog); dialogLockScreen(); tabCodeBlockDialog.show(); } else { var tabCodeBlockDialogHTML = ""; tabCodeBlockDialog = _this.createDialog({ name : tabCodeBlockDialogName, title : lang.dialog.tabCodeBlock.title, width : 750, height : 470, mask : settings.dialogShowMask, drag : settings.dialogDraggable, content : tabCodeBlockDialogHTML, lockScreen : settings.dialogLockScreen, maskStyle : { opacity : settings.dialogMaskOpacity, backgroundColor : settings.dialogMaskBgColor }, buttons : { enter : [lang.buttons.enter, function() { var codeTexts = this.find("textarea").val(); if (codeTexts === "") { alert(lang.dialog.tabCodeBlock.codeEmptyAlert); return false; } codeTexts = codeTexts.split("\n"); for (var i in codeTexts) { codeTexts[i] = " " + codeTexts[i]; } cm.replaceSelection(codeTexts.join("\n")); this.hide().lockScreen(false).hideMask(); return false; }], cancel : [lang.buttons.cancel, function() { this.hide().lockScreen(false).hideMask(); return false; }] } }); } }, "code-block" : function(cm) { var cursor = cm.getCursor(); var selection = cm.getSelection(); var codeBlockDialogName = classPrefix + "dialog-code-block", codeBlockDialog; if (editor.find("." + codeBlockDialogName).length > 0) { codeBlockDialog = editor.find("." + codeBlockDialogName); codeBlockDialog.find("option:first").attr("selected", "selected"); codeBlockDialog.find("textarea").val(selection); dialogShowMask(codeBlockDialog); dialogLockScreen(); codeBlockDialog.show(); } else { var codeBlockDialogHTML = "
    " + lang.dialog.codeBlock.selectLabel + "" + "
    " + ""; codeBlockDialog = _this.createDialog({ name : codeBlockDialogName, title : lang.dialog.codeBlock.title, width : 752, height : 565, mask : settings.dialogShowMask, drag : settings.dialogDraggable, content : codeBlockDialogHTML, lockScreen : settings.dialogLockScreen, maskStyle : { opacity : settings.dialogMaskOpacity, backgroundColor : settings.dialogMaskBgColor }, buttons : { enter : [lang.buttons.enter, function() { var codeTexts = this.find("textarea").val(); var langName = this.find("select").val(); if (langName === "") { alert(lang.dialog.codeBlock.unselectedLanguageAlert); return false; } if (codeTexts === "") { alert(lang.dialog.codeBlock.codeEmptyAlert); return false; } langName = (langName === "other") ? "" : langName; cm.replaceSelection(["```" + langName, codeTexts, "```"].join("\n")); if (langName === "") { cm.setCursor(cursor.line, cursor.ch + 3); } this.hide().lockScreen(false).hideMask(); return false; }], cancel : [lang.buttons.cancel, function() { this.hide().lockScreen(false).hideMask(); return false; }] } }); var langSelect = codeBlockDialog.find("select"); for (var key in editormd.codeLanguages) { var codeLang = editormd.codeLanguages[key]; langSelect.append(""); } langSelect.append(""); } }, datetime : function(cm) { var selection = cm.getSelection(); var date = new Date(); var langName = settings.lang.name; var datefmt = editormd.dateFormat() + " " + editormd.dateFormat((langName === "zh-cn" || langName === "zh-tw") ? "cn-week-day" : "week-day"); cm.replaceSelection(datefmt); }, watch : function(cm) { _this[_this.settings.watch ? "unwatch" : "watch"](); }, preview : function(cm) { _this.previewing(); }, fullscreen : function(cm) { _this.fullscreen(); }, clear : function(cm) { _this.clear(); }, info : function(cm) { _this.showInfoDialog(); } }; return (name && typeof toolbarIconHandlers[name] !== "undefined") ? toolbarHandlers[name] : toolbarHandlers; }, /** * 工具栏图标事件处理器 * @returns {editormd} 返回editormd的实例对象 */ setToolbarHandler : function() { var _this = this; var settings = this.settings; if (!settings.toolbar || settings.readOnly) { return ; } var editor = this.editor; var preview = this.preview; var toolbar = this.toolbar; var codeEditor = this.codeEditor; var codeMirror = this.codeMirror; var classPrefix = this.classPrefix; var previewContainer = this.previewContainer; var toolbarIcons = this.toolbarIcons = toolbar.find("." + classPrefix + "menu a"); toolbarIcons.bind(editormd.mouseOrTouch("click", "touchend"), function(event) { var icon = $(this).children(".fa"); var name = icon.attr("name"); var cursor = codeEditor.getCursor(); var selection = codeEditor.getSelection(); var toolbarIconHandlers = _this.getToolbarHandles(); if (name === "") { return ; } _this.activeIcon = icon; if (typeof toolbarIconHandlers[name] !== "undefined") { toolbarIconHandlers[name](codeEditor); } else { if (typeof settings.toolbarHandlers[name] !== "undefined") { $.proxy(settings.toolbarHandlers[name], _this)(codeEditor, icon, cursor, selection); } } if (name !== "link" && name !== "anchor" && name !== "image" && name !== "code-block" && name !== "code-block-tab" && name !== "watch" && name !== "preview" && name !== "fullscreen" && name !== "info") { codeEditor.focus(); } return false; }); return this; }, /** * 动态创建对话框 * @param {Object} options 配置项键值对 Key/Value * @returns {dialog} 返回创建的dialog的jQuery实例对象 */ createDialog : function(options) { var defaults = { name : "", width : 420, height: 240, title : "", drag : true, closed : true, content : "", mask : true, maskStyle : { backgroundColor : "#fff", opacity : 0.1 }, lockScreen : true, footer : true, buttons : false }; options = $.extend(true, defaults, options); var editor = this.editor; var classPrefix = this.classPrefix; var guid = (new Date).getTime(); var dialogName = ( (options.name === "") ? classPrefix + "dialog-" + guid : options.name); var mouseOrTouch = editormd.mouseOrTouch; var html = "
    "; if (options.title !== "") { html += "
    "; html += "" + options.title + ""; html += "
    "; } if (options.closed) { html += ""; } html += "
    " + options.content; if (options.footer || typeof options.footer === "string") { html += "
    " + ( (typeof options.footer === "boolean") ? "" : options.footer) + "
    "; } html += "
    "; html += "
    "; html += "
    "; html += "
    "; editor.append(html); var dialog = editor.find("." + dialogName); dialog.lockScreen = function(lock) { if (options.lockScreen) { $("html,body").css("overflow", (lock) ? "hidden" : ""); } return dialog; }; dialog.showMask = function() { if (options.mask) { editor.find("." + classPrefix + "mask").css(options.maskStyle).css("z-index", editormd.dialogZindex - 1).show(); } return dialog; }; dialog.hideMask = function() { if (options.mask) { editor.find("." + classPrefix + "mask").hide(); } return dialog; }; dialog.loading = function(show) { var loading = dialog.find("." + classPrefix + "dialog-mask"); loading[(show) ? "show" : "hide"](); return dialog; }; dialog.lockScreen(true).showMask(); dialog.show().css({ zIndex : editormd.dialogZindex, border : (editormd.isIE8) ? "1px solid #ddd" : "", width : (typeof options.width === "number") ? options.width + "px" : options.width, height : (typeof options.height === "number") ? options.height + "px" : options.height }); var dialogPosition = function(){ dialog.css({ top : ($(window).height() - dialog.height()) / 2 + "px", left : ($(window).width() - dialog.width()) / 2 + "px" }); }; dialogPosition(); $(window).resize(dialogPosition); dialog.children("." + classPrefix + "dialog-close").bind(mouseOrTouch("click", "touchend"), function() { dialog.hide().lockScreen(false).hideMask(); }); if (typeof options.buttons == "object") { var footer = dialog.footer = dialog.find("." + classPrefix + "dialog-footer"); for (var key in options.buttons) { var btn = options.buttons[key]; var btnClassName = classPrefix + key + "-btn"; footer.append(""); btn[1] = $.proxy(btn[1], dialog); footer.children("." + btnClassName).bind(mouseOrTouch("click", "touchend"), btn[1]); } } if (options.title !== "" && options.drag) { var posX, posY; var dialogHeader = dialog.children("." + classPrefix + "dialog-header"); if (!options.mask) { dialogHeader.bind(mouseOrTouch("click", "touchend"), function(){ editormd.dialogZindex += 2; dialog.css("z-index", editormd.dialogZindex); }); } dialogHeader.mousedown(function(e) { e = e || window.event; //IE posX = e.clientX - parseInt(dialog[0].style.left); posY = e.clientY - parseInt(dialog[0].style.top); document.onmousemove = moveAction; }); var userCanSelect = function (obj) { obj.removeClass(classPrefix + "user-unselect").off("selectstart"); } var userUnselect = function (obj) { obj.addClass(classPrefix + "user-unselect").on("selectstart", function(event) { // selectstart for IE return false; }); } var moveAction = function (e) { e = e || window.event; //IE var left, top, nowLeft = parseInt(dialog[0].style.left), nowTop = parseInt(dialog[0].style.top); if( nowLeft >= 0 ) { if( nowLeft + dialog.width() <= $(window).width()) { left = e.clientX - posX; } else { left = $(window).width() - dialog.width(); document.onmousemove = null; } } else { left = 0; document.onmousemove = null; } if( nowTop >= 0 ) { top = e.clientY - posY; } else { top = 0; document.onmousemove = null; } document.onselectstart = function() { return false; }; userUnselect($("body")); userUnselect(dialog); dialog[0].style.left = left + "px"; dialog[0].style.top = top + "px"; } document.onmouseup = function() { userCanSelect($("body")); userCanSelect(dialog); document.onselectstart = null; document.onmousemove = null; }; dialogHeader.touchDraggable = function() { var offset = null; var start = function(e) { var orig = e.originalEvent; var pos = $(this).parent().position(); offset = { x : orig.changedTouches[0].pageX - pos.left, y : orig.changedTouches[0].pageY - pos.top }; }; var move = function(e) { e.preventDefault(); var orig = e.originalEvent; $(this).parent().css({ top : orig.changedTouches[0].pageY - offset.y, left : orig.changedTouches[0].pageX - offset.x }); }; this.bind("touchstart", start).bind("touchmove", move); }; dialogHeader.touchDraggable(); } editormd.dialogZindex += 2; return dialog; }, /** * 创建关于Editor.md的对话框 * @returns {editormd} 返回editormd的实例对象 */ createInfoDialog : function() { var _this = this; var editor = this.editor; var classPrefix = this.classPrefix; var infoDialogHTML = [ "
    ", "
    ", "

    " + editormd.title + "v" + editormd.version + "

    ", "

    " + this.lang.description + "

    ", "

    Home page: " + editormd.homePage + "

    ", "

    License: MIT

    ", "
    ", "", "
    " ].join("\n"); editor.append(infoDialogHTML); var infoDialog = this.infoDialog = editor.find("." + classPrefix + "dialog-info"); infoDialog.find("." + classPrefix + "dialog-close").bind(editormd.mouseOrTouch("click", "touchend"), function() { _this.hideInfoDialog(); }); infoDialog.css("border", (editormd.isIE8) ? "1px solid #ddd" : "").show(); this.infoDialogPosition(); return this; }, /** * 关于Editor.md对话居中定位 * @returns {editormd} 返回editormd的实例对象 */ infoDialogPosition : function() { var infoDialog = this.infoDialog; var _infoDialogPosition = function() { infoDialog.css({ top : ($(window).height() - infoDialog.height()) / 2 + "px", left : ($(window).width() - infoDialog.width()) / 2 + "px" }); }; _infoDialogPosition(); $(window).resize(_infoDialogPosition); return this; }, /** * 显示关于Editor.md * @returns {editormd} 返回editormd的实例对象 */ showInfoDialog : function() { $("html,body").css("overflow-x", "hidden"); var _this = this; var editor = this.editor; var classPrefix = this.classPrefix; var infoDialog = this.infoDialog = editor.find("." + classPrefix + "dialog-info"); if (infoDialog.length < 1) { this.createInfoDialog(); } infoDialog.show(); this.infoDialogPosition(); return this; }, /** * 隐藏关于Editor.md * @returns {editormd} 返回editormd的实例对象 */ hideInfoDialog : function() { $("html,body").css("overflow-x", ""); this.infoDialog.hide(); return this; }, /** * 配置和初始化marked组件 * @returns {editormd} 返回editormd的实例对象 */ setMarked : function() { var marked = editormd.$marked; var markdownToC = this.markdownToC = []; marked.setOptions({ renderer : editormd.markedRenderer(markdownToC), gfm : true, tables : true, breaks : true, pedantic : false, sanitize : (this.settings.htmlDecode) ? false : true, // 关闭忽略HTML标签,即开启识别HTML标签,默认为false smartLists : true, smartypants : true }); return this; }, /** * 编辑器界面重建,用于动态语言包或模块加载等 * @returns {editormd} 返回editormd的实例对象 */ recreateEditor : function() { var _this = this; var editor = this.editor; var settings = this.settings; var toolbarIcons = settings.toolbarIcons(); if (typeof this.infoDialog !== "undefined") { this.infoDialog.remove(); } if (typeof toolbarIcons["info"] !== "undefined") { createInfoDialog(); } if (!settings.readOnly) { if (editor.find(".editormd-dialog").length > 0) { editor.find(".editormd-dialog").remove(); } this.getToolbarHandles(); this.setToolbar(); } this.resize(); return this; }, /** * 高亮预览HTML的pre代码部分 * @returns {editormd} 返回editormd的实例对象 */ previewCodeHighlight : function() { var settings = this.settings; var previewContainer = this.previewContainer; if (settings.previewCodeHighlight) { previewContainer.find("pre").addClass("prettyprint linenums"); prettyPrint(); } return this; }, /** * 解析TeX(KaTeX)科学公式 * @returns {editormd} 返回editormd的实例对象 */ katexRender : function() { var previewContainer = this.previewContainer; previewContainer.find("." + editormd.classNames.tex).each(function(){ var tex = $(this); editormd.$katex.render(tex.html(), tex[0]); }); return this; }, /** * 解析和渲染流程图及时序图 * @returns {editormd} 返回editormd的实例对象 */ flowChartAndSequenceDiagramRender : function() { var settings = this.settings; var previewContainer = this.previewContainer; if (editormd.isIE8) return ; if (settings.flowChart) { previewContainer.find(".flowchart").flowChart(); } if (settings.sequenceDiagram) { previewContainer.find(".sequence-diagram").sequenceDiagram({theme: "simple"}); } return this; }, /** * 加载队列完成之后的显示处理 * @returns {editormd} 返回editormd的实例对象 */ loadedDisplay : function() { var _this = this; var editor = this.editor; var preview = this.preview; var settings = this.settings; var codeEditor = this.codeEditor; var codeMirror = this.codeMirror; var previewContainer = this.previewContainer; var mouseOrTouch = editormd.mouseOrTouch; editor.find("." + this.classPrefix + "container-mask").hide(); this.saveToTextareas(); if(settings.watch) { preview.show(); } editor.data("oldWidth", editor.width()).data("oldHeight", editor.height()); // 为了兼容Zepto this.resize(); $(window).resize(function(){ _this.resize(); }); var codeEditorBindScroll = function() { codeMirror.find(".CodeMirror-scroll").bind(mouseOrTouch("scroll", "touchmove"), function() { var height = $(this).height(); var scrollTop = $(this).scrollTop(); var percent = (scrollTop / $(this)[0].scrollHeight); if (scrollTop === 0) { preview.scrollTop(0); } else if (scrollTop + height >= $(this)[0].scrollHeight) { preview.scrollTop(preview[0].scrollHeight); } else { preview.scrollTop(preview[0].scrollHeight * percent); } }); }; var codeEditorUnbindScroll = function() { codeMirror.find(".CodeMirror-scroll").unbind(mouseOrTouch("scroll", "touchmove")); }; var previewBindScroll = function() { preview.bind(mouseOrTouch("scroll", "touchmove"), function() { var height = $(this).height(); var scrollTop = $(this).scrollTop(); var percent = (scrollTop / $(this)[0].scrollHeight); var codeView = codeMirror.find(".CodeMirror-scroll"); if(scrollTop === 0) { codeView.scrollTop(0); } else if (scrollTop + height >= $(this)[0].scrollHeight) { codeView.scrollTop(codeView[0].scrollHeight); } else { codeView.scrollTop(codeView[0].scrollHeight * percent); } }); }; var previewUnbindScroll = function() { preview.unbind(mouseOrTouch("scroll", "touchmove")); }; codeMirror.bind({ mouseover : codeEditorBindScroll, mouseout : codeEditorUnbindScroll, touchstart : codeEditorBindScroll, touchend : codeEditorUnbindScroll }); preview.bind({ mouseover : previewBindScroll, mouseout : previewUnbindScroll, touchstart : previewBindScroll, touchend : previewUnbindScroll }); codeEditor.on("change", function(cm, changeObj) { _this.saveToTextareas(); }); $.proxy(settings.onload, this)(); this.state.loaded = true; return this; }, /** * 设置编辑器的宽度 * @param {Number|String} width 编辑器宽度值 * @returns {editormd} 返回editormd的实例对象 */ width : function(width) { this.editor.css("width", (typeof width === "number") ? width + "px" : width); this.resize(); return this; }, /** * 设置编辑器的高度 * @param {Number|String} height 编辑器高度值 * @returns {editormd} 返回editormd的实例对象 */ height : function(height) { this.editor.css("height", (typeof height === "number") ? height + "px" : height); this.resize(); return this; }, /** * 调整编辑器的尺寸和布局 * @param {Number|String} [width=null] 编辑器宽度值 * @param {Number|String} [height=null] 编辑器高度值 * @returns {editormd} 返回editormd的实例对象 */ resize : function(width, height) { width = width || null; height = height || null; var editor = this.editor; var preview = this.preview; var toolbar = this.toolbar; var settings = this.settings; var codeEditor = this.codeEditor; var codeMirror = this.codeMirror; if(width && height) { editor.css({ width : (typeof width === "number") ? width + "px" : width, height : (typeof height === "number") ? height + "px" : height }); } if (settings.toolbar && !settings.readOnly) { codeMirror.css("margin-top", toolbar.height() + 1).height(editor.height() - toolbar.height()); } else { codeMirror.css("margin-top", 0).height(editor.height()); } codeMirror.find(".CodeMirror-gutters").height(codeMirror.height()); if(settings.watch) { codeMirror.width(editor.width() / 2); preview.width((!this.state.preview) ? editor.width() / 2 : editor.width()); if (settings.toolbar && !settings.readOnly) { preview.css("top", toolbar.height()).height(editor.height() - toolbar.height()); } else { preview.css("top", 0).height(editor.height()); } } else { codeMirror.width(editor.width()); preview.hide(); } return this; }, /** * 分别将Markdown源码和解析的HTML源码保存到对应的textarea * @returns {editormd} 返回editormd的实例对象 */ saveToTextareas : function() { var _this = this; var state = this.state; var settings = this.settings; var codeEditor = this.codeEditor; var previewContainer = this.previewContainer; var codeMirrorValue = codeEditor.getValue(); var markdownToC = this.markdownToC = []; var newMarkdownDoc = editormd.$marked(codeMirrorValue, {renderer : editormd.markedRenderer(markdownToC)}); this.markdownTextarea[0].innerText = codeMirrorValue; codeEditor.save(); if (settings.saveHTMLToTextarea) { this.htmlTextarea.html(newMarkdownDoc); } if(settings.watch || (!settings.watch && state.preview)) { previewContainer.html(newMarkdownDoc); this.previewCodeHighlight(); if (settings.toc) { editormd.markdownToCRenderer(markdownToC, previewContainer, settings.tocStartLevel); } if (!settings.inRequirejs && !editormd.kaTeXLoaded) { editormd.loadKaTeX(function(){ editormd.$katex = katex; editormd.kaTeXLoaded = true; _this.katexRender(); }); } else { this.katexRender(); } var timer = setTimeout(function(){ clearTimeout(timer); _this.flowChartAndSequenceDiagramRender(); }, 10); if (state.loaded) { $.proxy(settings.onchange, this)(); } } return this; }, /** * 聚焦光标位置 * @returns {editormd} 返回editormd的实例对象 */ focus : function() { this.codeEditor.focus(); return this; }, /** * 设置光标的位置 * @param {Object} cursor 要设置的光标位置键值对象,例:{line:1, ch:0} * @returns {editormd} 返回editormd的实例对象 */ setCursor : function(cursor) { this.codeEditor.setCursor(cursor); return this; }, /** * 获取当前光标的位置 * @returns {Cursor} 返回一个光标Cursor对象 */ getCursor : function() { return this.codeEditor.getCursor(); }, /** * 设置光标选中的范围 * @param {Object} from 开始位置的光标键值对象,例:{line:1, ch:0} * @param {Object} to 结束位置的光标键值对象,例:{line:1, ch:0} * @returns {editormd} 返回editormd的实例对象 */ setSelection : function(from, to) { this.codeEditor.setSelection(from, to); return this; }, /** * 获取光标选中的文本 * @returns {String} 返回选中文本的字符串形式 */ getSelection : function() { return this.codeEditor.getSelection(); }, /** * 替换当前光标选中的文本或在当前光标处插入新字符 * @param {String} value 要插入的字符值 * @returns {editormd} 返回editormd的实例对象 */ replaceSelection : function(value) { this.codeEditor.replaceSelection(value); return this; }, /** * 在当前光标处插入新字符 * * 同replaceSelection()方法 * @param {String} value 要插入的字符值 * @returns {editormd} 返回editormd的实例对象 */ insertValue : function(value) { this.replaceSelection(value); return this; }, /** * 设置和传入编辑器的markdown源文档 * @param {String} md 要传入的markdown源文档 * @returns {editormd} 返回editormd的实例对象 */ setMarkdown : function(md) { var settings = this.settings; this.codeEditor.setValue(md); return this; }, /** * 获取编辑器的markdown源文档 * @returns {editormd} 返回editormd的实例对象 */ getMarkdown : function() { return this.codeEditor.getValue(); }, /** * 清空编辑器 * @returns {editormd} 返回editormd的实例对象 */ clear : function() { this.codeEditor.setValue(""); return this; }, /** * 获取解析后存放在Textarea的HTML源码 * @returns {String} 返回HTML源码 */ getHTML : function() { if (!this.settings.saveHTMLToTextarea) { alert("Error: settings.saveHTMLToTextarea == false"); return false; } return this.htmlTextarea.html(); }, /** * getHTML()的别名 * @returns {editormd} 返回HTML源码 */ getTextareaSavedHTML : function() { return this.getHTML(); }, /** * 获取预览窗口的HTML源码 * @returns {editormd} 返回editormd的实例对象 */ getPreviewedHTML : function() { if (!this.settings.watch) { alert("Error: settings.watch == false"); return false; } return this.previewContainer.html(); }, /** * 开启实时预览 * @returns {editormd} 返回editormd的实例对象 */ watch : function(callback) { callback = callback || function() {}; this.state.watching = this.settings.watch = true; this.preview.show(); var watchIcon = this.settings.toolbarIconsClass.watch; var unWatchIcon = this.settings.toolbarIconsClass.unwatch; var icon = this.toolbar.find(".fa[name=watch]"); icon.parent().attr("title", this.settings.lang.toolbar.watch); icon.removeClass(unWatchIcon).addClass(watchIcon); this.codeMirror.css("border-right", "1px solid #ddd").width(this.editor.width() / 2); this.saveToTextareas().resize(); $.proxy(callback, this)(); return this; }, /** * 关闭实时预览 * @returns {editormd} 返回editormd的实例对象 */ unwatch : function(callback) { callback = callback || function() {}; this.state.watching = this.settings.watch = false; this.preview.hide(); var watchIcon = this.settings.toolbarIconsClass.watch; var unWatchIcon = this.settings.toolbarIconsClass.unwatch; var icon = this.toolbar.find(".fa[name=watch]"); icon.parent().attr("title", this.settings.lang.toolbar.unwatch); icon.removeClass(watchIcon).addClass(unWatchIcon); this.codeMirror.css("border-right", "none").width(this.editor.width()); this.resize(); $.proxy(callback, this)(); return this; }, /** * 显示编辑器 * @param {Function} [callback=function()] 回调函数 * @returns {editormd} 返回editormd的实例对象 */ show : function(callback) { callback = callback || function() {}; var _this = this; this.editor.show(function(){ $.proxy(callback, _this)(); }); return this; }, /** * 隐藏编辑器 * @param {Function} [callback=function()] 回调函数 * @returns {editormd} 返回editormd的实例对象 */ hide : function(callback) { callback = callback || function() {}; var _this = this; this.editor.hide(function(){ $.proxy(callback, _this)(); }); return this; }, /** * 隐藏编辑器部分,只预览HTML * @returns {editormd} 返回editormd的实例对象 */ previewing : function() { var _this = this; var editor = this.editor; var preview = this.preview; var toolbar = this.toolbar; var settings = this.settings; var codeMirror = this.codeMirror; if (settings.toolbar) { toolbar.toggle(); toolbar.find(".fa[name=preview]").toggleClass("active"); } codeMirror.toggle(); if(codeMirror.css("display") === "none") // 为了兼容Zepto,而不使用codeMirror.is(":hidden") { this.state.preview = true; if(this.state.fullscreen) { preview.css("background", "#fff"); } editor.find("." + this.classPrefix + "preview-close-btn").show().bind(editormd.mouseOrTouch("click", "touchend"), function(){ _this.previewed(); }); if(!settings.watch) { /*var codeEditor = this.codeEditor; var previewContainer = this.previewContainer; codeEditor.save(); var markdownToC = this.markdownToC = []; var newMarkdownDoc = editormd.$marked(codeEditor.getValue(), {renderer : editormd.markedRenderer(markdownToC)}); previewContainer.html(newMarkdownDoc); if (settings.toc) { editormd.markdownToCRenderer(markdownToC, previewContainer, settings.tocStartLevel); }*/ this.saveToTextareas(); } preview.show().css({ position : "static", top : 0, width : editor.width(), height : editor.height() }); } else { this.previewed(); } $(window).keyup(function(event) { if (event.keyCode === 27) { _this.previewed(); } }); }, /** * 显示编辑器部分,退出只预览HTML * @returns {editormd} 返回editormd的实例对象 */ previewed : function() { var editor = this.editor; var preview = this.preview; var toolbar = this.toolbar; var settings = this.settings; var codeMirror = this.codeMirror; var previewCloseBtn = editor.find("." + this.classPrefix + "preview-close-btn"); this.state.preview = false; codeMirror.show(); if (settings.toolbar) { toolbar.show(); } if(this.settings.watch) { preview.show(); } else { preview.hide(); } previewCloseBtn.hide().bind(editormd.mouseOrTouch("click", "touchend")); preview.css({ background : null, position : "absolute", width : editor.width() / 2, height : editor.height() - toolbar.height(), top : (settings.toolbar) ? toolbar.height() : 0 }); return this; }, /** * 编辑器全屏显示 * @returns {editormd} 返回editormd的实例对象 */ fullscreen : function() { var _this = this; var editor = this.editor; var preview = this.preview; var toolbar = this.toolbar; var fullscreenClass = this.classPrefix + "fullscreen"; toolbar.find(".fa[name=fullscreen]").parent().toggleClass("active"); if (!editor.hasClass(fullscreenClass)) { this.state.fullscreen = true; $("html,body").css("overflow", "hidden"); editor.css({ position : "fixed", top : 0, left : 0, margin : 0, border : "none", width : $(window).width(), height : $(window).height() }).addClass(fullscreenClass); this.resize(); $.proxy(this.settings.onfullscreen, this)(); } else { this.fullscreenExit(); } $(window).keyup(function(event) { if (_this.state.preview) { return ; } if (event.keyCode === 27) { _this.fullscreenExit(); } }); return this; }, /** * 编辑器退出全屏显示 * @returns {editormd} 返回editormd的实例对象 */ fullscreenExit : function() { var editor = this.editor; var fullscreenClass = this.classPrefix + "fullscreen"; this.state.fullscreen = false; this.toolbar.find(".fa[name=fullscreen]").parent().removeClass("active"); $("html,body").css("overflow", ""); editor.css({ position : "", top : "", left : "", margin : "0 auto", width : editor.data("oldWidth"), height : editor.data("oldHeight"), border : "1px solid #ddd" }).removeClass(fullscreenClass); this.resize(); $.proxy(this.settings.onfullscreenExit, this)(); return this; } }; editormd.fn.init.prototype = editormd.fn; /** * 清除字符串两边的空格 * @param {String} str string * @returns {String} trim string */ editormd.trim = function(str) { return (!String.prototype.trim) ? str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '') : str.trim(); }; var trim = editormd.trim; /** * 自定义marked的解析器 * @param {Array} markdownToC 传入用于接收TOC的数组 * @returns {Renderer} markedRenderer 返回marked的Renderer自定义对象 */ editormd.markedRenderer = function(markdownToC) { var marked = editormd.$marked; var markedRenderer = new marked.Renderer(); markdownToC = markdownToC || []; markedRenderer.heading = function(text, level, raw) { var linkText = text; var hasLinkReg = /\s*\]*)\>(.*)\<\/a\>\s*/; var getLinkTextReg = /\s*\]+)\>([^\>]*)\<\/a\>\s*/g; if (hasLinkReg.test(text)) { var tempText = []; text = text.split(/\]+)\>([^\>]*)\<\/a\>/); for (var i = 0, len = text.length; i < len; i++) { tempText.push(text[i].replace(/\s*href\=\"(.*)\"\s*/g, "")); } text = tempText.join(" "); } text = trim(text); var escapedText = text.toLowerCase().replace(/[^\w]+/g, "-"); var toc = { text : text, level : level, slug : escapedText }; var isChinese = /^[\u4e00-\u9fa5]+$/.test(text); var id = (isChinese) ? escape(text).replace(/\%/g, "") : text.toLowerCase().replace(/[^\w]+/g, "-"); markdownToC.push(toc); var headingHTML = ""; headingHTML += ""; headingHTML += ""; headingHTML += (hasLinkReg) ? linkText : text; headingHTML += ""; return headingHTML; }; markedRenderer.paragraph = function(text) { var isTeXInline = /\$\$(.*)\$\$/g.test(text); var isTeXLine = /^\$\$(.*)\$\$$/.test(text); var isTeXAddClass = (isTeXLine) ? " class=\"" + editormd.classNames.tex + "\"" : ""; var isToC = /^\[TOC\]$/.test(text); if (!isTeXLine && isTeXInline) { text = text.replace(/(\$\$([^\$]*)\$\$)+/g, function($1, $2) { return "" + $2.replace(/\$/g, "") + ""; }); } else { text = (isTeXLine) ? text.replace(/\$/g, "") : text; } return (isToC) ? "
      " + text + "
    " : "" + text + "

    \n"; }; markedRenderer.code = function (code, lang, escaped) { if (lang === "seq" || lang === "sequence") { return "
    " + code + "
    "; } else if ( lang === "flow") { return "
    " + code + "
    "; } else { return marked.Renderer.prototype.code.apply(this, arguments); } }; return markedRenderer; }; /** * 生成TOC(Table of Contents) * @param {Array} toc 从marked获取的TOC数组列表 * @param {Element} container 插入TOC的容器元素 * @param {Integer} startLevel Hx 起始层级 * @returns {Object} tocContainer 返回ToC列表容器层的jQuery对象元素 */ editormd.markdownToCRenderer = function(toc, container, startLevel) { var html = ""; var lastLevel = 0; startLevel = startLevel || 1; for (var i = 0, len = toc.length; i < len; i++) { var text = toc[i].text; var level = toc[i].level; if (level < startLevel) { continue; } if (level > lastLevel) { html += ""; } else if (level < lastLevel) { html += (new Array(lastLevel - level + 2)).join(""); } else { html += ""; } html += "
  • " + text + "
      "; lastLevel = level; } var tocContainer = container.find('.markdown-toc'); tocContainer.children('.markdown-toc-list').html("").html(html); return tocContainer; }; /** * 将Markdown文档解析为HTML用于前台显示 * @param {String} id 用于显示HTML的对象ID * @param {Object} [options={}] 配置选项,可选 * @returns {Object} div 返回jQuery对象元素 */ editormd.markdownToHTML = function(id, options) { options = options || {}; var defaults = { toc : true, tocStartLevel : 2, markdown : "", htmlDecode : false, inRequirejs : false, tex : false, flowChart : false, sequenceDiagram : false, previewCodeHighlight : true }; editormd.$marked = marked; var div = $("#" + id); var settings = div.settings = $.extend(true, defaults, options); var saveTo = div.find("textarea"); var markdownDoc = (settings.markdown === "") ? saveTo.val() : settings.markdown; var markdownToC = []; var markedOptions = { renderer : editormd.markedRenderer(markdownToC), gfm : true, tables : true, breaks : true, pedantic : false, sanitize : (settings.htmlDecode) ? false : true, // 是否忽略HTML标签,即是否开启HTML标签解析,为了安全性,默认不开启 smartLists : true, smartypants : true }; var markdownParsed = marked(markdownDoc, markedOptions); if (editormd.isIE8) { saveTo.val(markdownDoc); } else { saveTo.html(markdownDoc); } div.addClass("markdown-body " + this.classPrefix + "html-preview").append(markdownParsed); if (settings.toc) { div.tocContainer = this.markdownToCRenderer(markdownToC, div, settings.tocStartLevel); } if (settings.previewCodeHighlight) { div.find("pre").addClass("prettyprint linenums"); prettyPrint(); } if (!editormd.isIE8) { if (settings.flowChart) { div.find(".flowchart").flowChart(); } if (settings.sequenceDiagram) { div.find(".sequence-diagram").sequenceDiagram({theme: "simple"}); } } if (settings.tex) { var katexHandle = function() { div.find("." + editormd.classNames.tex).each(function(){ var tex = $(this); editormd.$katex.render(tex.html(), tex[0]); }); }; if (!settings.inRequirejs) { this.loadKaTeX(function(){ editormd.$katex = katex; katexHandle(); }); } else { katexHandle(); } } div.getMarkdown = function() { return (editormd.isIE8) ? saveTo.val() : saveTo.html(); }; return div; }; // for CodeBlock dialog select editormd.codeLanguages = { asp : "ASP", actionscript : "ActionScript(3.0)/Flash/Flex", bash : "Bash/Bat", css : "CSS", c : "C", cpp : "C++", csharp : "C#", coffeescript : "CoffeeScript", d : "D", dart : "Dart", delphi : "Delphi/Pascal", erlang : "Erlang", go : "Golang", groovy : "Groovy", html : "HTML", java : "Java", json : "JSON", javascript : "Javascript", lua : "Lua", less : "LESS", markdown : "Markdown", "objective-c" : "Objective-C", php : "PHP", perl : "Perl", python : "Python", r : "R", rst : "reStructedText", ruby : "Ruby", sql : "SQL", sass : "SASS/SCSS", shell : "Shell", scala : "Scala", swift : "Swift", vb : "VB/VBScript", xml : "XML", yaml : "YAML" }; // for Requires.js and Gulpfile.js editormd.codeMirrorModules = { modes : [ "css", "sass", "shell", "sql", "clike", "php", "xml", "markdown", "javascript", "htmlmixed", "gfm", "http", "go", "dart", "coffeescript", "nginx", "python", "perl", "lua", "r", "ruby", "rst", "smartymixed", "vb", "vbscript", "velocity", "xquery", "yaml", "erlang", "jade" ], addons : [ "edit/trailingspace", "dialog/dialog", "search/searchcursor", "search/search", "scroll/annotatescrollbar", "search/matchesonscrollbar", "display/placeholder", "edit/closetag", "fold/xml-fold", "mode/overlay", "selection/active-line", "edit/closebrackets", "display/fullscreen", "search/searchcursor", "search/match-highlighter" ] }; /** * 用于支持Require.js加载的方法,注入 * @param {Function} CodeMirror CodeMirror对象 * @param {Function} marked marked对象 * @param {Function} prettyPrint prettyPrint函数 */ editormd.requirejsInit = function(CodeMirror, marked, katex, prettyPrint) { editormd.$CodeMirror = CodeMirror; editormd.$marked = marked; editormd.$katex = katex; editormd.$prettyPrint = prettyPrint; }; /** * 用于Require.js加载的模块队列 * @param {String} [loadPath=""] 基本路径,默认为空 * @returns {Array} modules 返回队列路径数组 */ editormd.requireModules = function(loadPath) { loadPath = loadPath || ""; var settings = editormd.defaults; var modules = []; modules.push(loadPath + "codemirror/codemirror.min"); var codeMirrorModules = editormd.codeMirrorModules; for (var i = 0, len = codeMirrorModules.modes.length; i < len; i++) { var modeName = codeMirrorModules.modes[i]; modules.push(loadPath + "codemirror/mode/" + modeName + "/" + modeName); } for (var i = 0, len = codeMirrorModules.addons.length; i < len; i++) { var addonName = codeMirrorModules.addons[i]; modules.push(loadPath + "codemirror/addon/" + addonName); } editormd.loadCSS(editormd.katexURL.css); return modules; }; /** * 动态加载CSS文件的方法 * @param {String} fileName CSS文件名 * @param {Function} [callback=function()] 加载成功后执行的回调函数 * @param {String} [into="head"] 嵌入页面的位置 */ editormd.loadCSS = function(fileName, callback, into) { into = into || "head"; callback = callback || function() {}; var css = document.createElement("link"); css.type = "text/css"; css.rel = "stylesheet"; css.onload = css.onreadystatechange = function() { callback(); }; css.href = fileName + ".css"; if(into === "head") { document.getElementsByTagName("head")[0].appendChild(css); } else { document.body.appendChild(css); } }; editormd.isIE = (navigator.appName == "Microsoft Internet Explorer"); editormd.isIE8 = (editormd.isIE && navigator.appVersion.match(/8./i) == "8."); /** * 动态加载JS文件的方法 * @param {String} fileName JS文件名 * @param {Function} [callback=function()] 加载成功后执行的回调函数 * @param {String} [into="head"] 嵌入页面的位置 */ editormd.loadScript = function(fileName, callback, into) { into = into || "head"; callback = callback || function() {}; var script = null; script = document.createElement("script"); script.id = fileName.replace(/[\./]+/g, "-"); script.type = "text/javascript"; script.src = fileName + ".js"; if (editormd.isIE8) { script.onreadystatechange = function() { if(script.readyState) { if (script.readyState === "loaded" || script.readyState === "complete") { script.onreadystatechange = null; callback(); } } }; } else { script.onload = function() { callback(); }; } if (into === "head") { document.getElementsByTagName("head")[0].appendChild(script); } else { document.body.appendChild(script); } }; // 使用国外的CDN,加载速度有时会很慢,或者自定义URL editormd.katexURL = { css : "//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.1.1/katex.min", js : "//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.1.1/katex.min" }; editormd.kaTeXLoaded = false; /** * 加载KaTex文件 * @param {Function} [callback=function()] 加载成功后执行的回调函数 */ editormd.loadKaTeX = function (callback) { editormd.loadCSS(editormd.katexURL.css, function(){ editormd.loadScript(editormd.katexURL.js, callback || function(){}); }); }; /** * 鼠标和触摸事件的判断/选择方法 * @param {String} [mouseEventType="click"] 供选择的鼠标事件 * @param {String} [touchEventType="touchend"] 供选择的触摸事件 * @returns {String} EventType 返回事件类型名称 */ editormd.mouseOrTouch = function(mouseEventType, touchEventType) { mouseEventType = mouseEventType || "click"; touchEventType = touchEventType || "touchend"; var eventType = mouseEventType; try { document.createEvent("TouchEvent"); eventType = touchEventType; } catch(e) { } return eventType; }; /** * 日期时间的格式化方法 * @param {String} [format=""] 日期时间的格式,类似PHP的格式 * @returns {String} datefmt 返回格式化后的日期时间字符串 */ editormd.dateFormat = function(format) { format = format || ""; var addZero = function(d) { return (d < 10) ? "0" + d : d; }; var date = new Date; var year = date.getFullYear(); var year2 = year.toString().slice(2, 4); var month = addZero(date.getMonth() + 1); var day = addZero(date.getDate()); var weekDay = date.getDay(); var hour = addZero(date.getHours()); var min = addZero(date.getMinutes()); var second = addZero(date.getSeconds()); var ms = addZero(date.getMilliseconds()); var datefmt = ""; var ymd = year2 + "-" + month + "-" + day; var fymd = year + "-" + month + "-" + day; var hms = hour + ":" + min + ":" + second; switch (format) { case "UNIX Time" : datefmt = date.getTime(); break; case "UTC" : datefmt = date.toUTCString(); break; case "yy" : datefmt = year2; break; case "year" : case "yyyy" : datefmt = year; break; case "month" : case "mm" : datefmt = month; break; case "cn-week-day" : case "cn-wd" : var cnWeekDays = ["日", "一", "二", "三", "四", "五", "六"]; datefmt = "星期" + cnWeekDays[weekDay]; break; case "week-day" : case "wd" : var weekDays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; datefmt = weekDays[weekDay]; break; case "day" : case "dd" : datefmt = day; break; case "hour" : case "hh" : datefmt = hour; break; case "min" : case "ii" : datefmt = min; break; case "second" : case "ss" : datefmt = second; break; case "ms" : datefmt = ms; break; case "yy-mm-dd" : datefmt = ymd; break; case "yyyy-mm-dd" : datefmt = fymd; break; case "yyyy-mm-dd h:i:s ms" : case "full + ms" : datefmt = fymd + " " + hms + " " + ms; break; case "full" : case "yyyy-mm-dd h:i:s" : default: datefmt = fymd + " " + hms; break; }; return datefmt; }; return editormd; }));