You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4667 lines
158 KiB

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