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.

4342 lines
120 KiB

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