((function(){ var editors = []; var toolbarIdentifiers = [ 'bold', 'italic', 'strike', 'link', 'image', 'blockquote', 'listUl', 'listOl' ]; if (typeof window.customToolbarElements !== 'undefined') { window.customToolbarElements.forEach(function(customToolbarElement) { toolbarIdentifiers.push(customToolbarElement.identifier); }); } var toolbarButtons = { fullscreen: { title : 'Fullscreen', label : '' }, bold : { title : 'Bold', label : '' }, italic : { title : 'Italic', label : '' }, strike : { title : 'Strikethrough', label : '' }, blockquote : { title : 'Blockquote', label : '' }, link : { title : 'Link', label : '' }, image : { title : 'Image', label : '' }, listUl : { title : 'Unordered List', label : '' }, listOl : { title : 'Ordered List', label : '' } }; if (typeof window.customToolbarElements !== 'undefined') { window.customToolbarElements.forEach(function(customToolbarElement) { toolbarButtons[customToolbarElement.identifier] = customToolbarElement.button; }); } var debounce = function(func, wait, immediate) { var timeout; return function() { var context = this, args = arguments; var later = function() { timeout = null; if (!immediate) func.apply(context, args); }; var callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) func.apply(context, args); }; }; var template = [ '
', '
', '', '
', '', '
', '', '
', '
', '
', '
', '
', '
' ].join(''); var MDEditor = function(editor, options){ var tpl = template, $this = this, task = 'task' + GravAdmin.config.param_sep; this.defaults = { markdown : false, autocomplete : true, height : 500, codemirror : { mode: 'htmlmixed', theme: 'paper', lineWrapping: true, dragDrop: true, autoCloseTags: true, matchTags: true, autoCloseBrackets: true, matchBrackets: true, indentUnit: 4, indentWithTabs: false, tabSize: 4, hintOptions: {completionSingle:false}, extraKeys: {"Enter": "newlineAndIndentContinueMarkdownList"} }, toolbar : toolbarIdentifiers, lblPreview : '', lblCodeview : '', lblMarkedview: '' } this.element = $(editor); this.options = $.extend({}, this.defaults, options); this.CodeMirror = CodeMirror; this.buttons = {}; tpl = tpl.replace(/\{:lblPreview\}/g, this.options.lblPreview); tpl = tpl.replace(/\{:lblCodeview\}/g, this.options.lblCodeview); this.mdeditor = $(tpl); this.content = this.mdeditor.find('.grav-mdeditor-content'); this.toolbar = this.mdeditor.find('.grav-mdeditor-toolbar'); this.preview = this.mdeditor.find('.grav-mdeditor-preview').children().eq(0); this.code = this.mdeditor.find('.grav-mdeditor-code'); this.element.before(this.mdeditor).appendTo(this.code); this.editor = this.CodeMirror.fromTextArea(this.element[0], this.options.codemirror); this.editor.mdeditor = this; if (this.options.markdown) { this.editor.setOption('mode', 'gfm'); } this.editor.on('change', debounce(function() { $this.render(); }, 150)); this.editor.on('change', function() { $this.editor.save(); }); this.code.find('.CodeMirror').css('height', this.options.height); var editor = this.editor; $("#gravDropzone").delegate('[data-dz-insert]', 'click', function(e) { var target = $(e.currentTarget).parent('.dz-preview').find('.dz-filename'); editor.focus(); var filename = encodeURI(target.text()); filename = filename.replace(/@3x|@2x|@1x/, ''); filename = filename.replace(/\(/g, '%28'); filename = filename.replace(/\)/g, '%29'); if (filename.match(/\.(jpg|jpeg|png|gif)$/)) { editor.doc.replaceSelection('![](' + filename + ')'); } else { editor.doc.replaceSelection('[' + decodeURI(filename) + '](' + filename + ')'); } }); this.preview.container = this.preview; this.mdeditor.on('click', '.grav-mdeditor-button-code, .grav-mdeditor-button-preview', function(e) { var task = 'task' + GravAdmin.config.param_sep; e.preventDefault(); if ($this.mdeditor.attr('data-mode') == 'tab') { if ($(this).hasClass('grav-mdeditor-button-preview')) { GravAjax({ dataType: 'JSON', url: $this.element.data('grav-urlpreview') + '/' + task + 'processmarkdown', method: 'post', data: $this.element.parents('form').serialize(), toastErrors: true, success: function (response) { $this.preview.container.html(response.message); } }); } $this.mdeditor.find('.grav-mdeditor-button-code, .grav-mdeditor-button-preview').removeClass('mdeditor-active').filter(this).addClass('mdeditor-active'); $this.activetab = $(this).hasClass('grav-mdeditor-button-code') ? 'code' : 'preview'; $this.mdeditor.attr('data-active-tab', $this.activetab); $this.editor.refresh(); if ($this.activetab == 'preview') { $('.grav-mdeditor-toolbar').fadeOut(); setTimeout(function() { $('.grav-mdeditor-preview-text').fadeIn(); }, 500); } else { $('.grav-mdeditor-preview-text').fadeOut(); setTimeout(function() { $('.grav-mdeditor-toolbar').fadeIn(); }, 500); } } }); this.mdeditor.on('click', 'a[data-mdeditor-button]', function() { if (!$this.code.is(':visible')) return; $this.element.trigger('action.' + $(this).data('mdeditor-button'), [$this.editor]); }); this.preview.parent().css('height', this.code.height()); // autocomplete if (this.options.autocomplete && this.CodeMirror.showHint && this.CodeMirror.hint && this.CodeMirror.hint.html) { this.editor.on('inputRead', debounce(function() { var doc = $this.editor.getDoc(), POS = doc.getCursor(), mode = $this.CodeMirror.innerMode($this.editor.getMode(), $this.editor.getTokenAt(POS).state).mode.name; if (mode == 'xml') { //html depends on xml var cur = $this.editor.getCursor(), token = $this.editor.getTokenAt(cur); if (token.string.charAt(0) == '<' || token.type == 'attribute') { $this.CodeMirror.showHint($this.editor, $this.CodeMirror.hint.html, { completeSingle: false }); } } }, 100)); } this.debouncedRedraw = debounce(function () { $this.redraw(); }, 5); /*this.element.attr('data-grav-check-display', 1).on('grav-check-display', function(e) { if($this.mdeditor.is(":visible")) $this.fit(); });*/ editors.push(this); // Methods this.addButton = function(name, button) { this.buttons[name] = button; }; this.addButtons = function(buttons) { $.extend(this.buttons, buttons); }; this._buildtoolbar = function() { if (!(this.options.toolbar && this.options.toolbar.length)) return; var $this = this, bar = []; this.toolbar.empty(); this.options.toolbar.forEach(function(button) { if (!$this.buttons[button]) return; var title = $this.buttons[button].title ? $this.buttons[button].title : button; var buttonClass = $this.buttons[button].class ? 'class="' + $this.buttons[button].class + '"' : ''; bar.push('
  • '+$this.buttons[button].label+'
  • '); }); this.toolbar.html(bar.join('\n')); }; this.fit = function() { var mode = this.options.mode; if (mode == 'split' && this.mdeditor.width() < this.options.maxsplitsize) { mode = 'tab'; } if (mode == 'tab') { if (!this.activetab) { this.activetab = 'code'; this.mdeditor.attr('data-active-tab', this.activetab); } this.mdeditor.find('.grav-mdeditor-button-code, .grav-mdeditor-button-preview').removeClass('uk-active') .filter(this.activetab == 'code' ? '.grav-mdeditor-button-code' : '.grav-mdeditor-button-preview') .addClass('uk-active'); } this.editor.refresh(); this.preview.parent().css('height', this.code.height()); this.mdeditor.attr('data-mode', mode); }; this.redraw = function() { this._buildtoolbar(); this.render(); this.fit(); }; this.getMode = function() { return this.editor.getOption('mode'); }; this.getCursorMode = function() { var param = { mode: 'html'}; this.element.trigger('cursorMode', [param]); return param.mode; }; this.render = function() { this.currentvalue = this.editor.getValue().replace(/^---([\s\S]*?)---\n{1,}/g, ''); // empty code if (!this.currentvalue) { this.element.val(''); this.preview.container.html(''); return; } this.element.trigger('render', [this]); this.element.trigger('renderLate', [this]); this.preview.container.html(this.currentvalue); }; this.addShortcut = function(name, callback) { var map = {}; if (!$.isArray(name)) { name = [name]; } name.forEach(function(key) { map[key] = callback; }); this.editor.addKeyMap(map); return map; }; this.addShortcutAction = function(action, shortcuts) { var editor = this; this.addShortcut(shortcuts, function() { editor.element.trigger('action.' + action, [editor.editor]); }); }; this.replaceSelection = function(replace, action) { var text = this.editor.getSelection(), indexOf = -1, cur = this.editor.getCursor(), curLine = this.editor.getLine(cur.line), start = cur.ch, end = start; if (!text.length) { while (end < curLine.length && /[\w$]+/.test(curLine.charAt(end))) ++end; while (start && /[\w$]+/.test(curLine.charAt(start - 1))) --start; var curWord = start != end && curLine.slice(start, end); if (curWord) { this.editor.setSelection({ line: cur.line, ch: start}, { line: cur.line, ch: end }); text = curWord; } else { indexOf = replace.indexOf('$1'); } } var html = replace.replace('$1', text); this.editor.replaceSelection(html, 'end'); if (indexOf !== -1) { this.editor.setCursor({ line: cur.line, ch: start + indexOf }); } else { if (action == 'link' || action == 'image') { this.editor.setCursor({ line: cur.line, ch: html.length -1 }); } } this.editor.focus(); }; this.replaceLine = function(replace, action) { var pos = this.editor.getDoc().getCursor(), text = this.editor.getLine(pos.line), html = replace.replace('$1', text); this.editor.replaceRange(html , { line: pos.line, ch: 0 }, { line: pos.line, ch: text.length }); this.editor.setCursor({ line: pos.line, ch: html.length }); this.editor.focus(); }; this.save = function() { this.editor.save(); }; this._initToolbar = function(editor) { editor.addButtons(toolbarButtons); addAction('bold', '**$1**'); addAction('italic', '_$1_'); addAction('strike', '~~$1~~'); addAction('blockquote', '> $1', 'replaceLine'); addAction('link', '[$1](http://)'); addAction('image', '![$1](http://)'); editor.element.on('action.listUl', function() { if (editor.getCursorMode() == 'markdown') { var cm = editor.editor, pos = cm.getDoc().getCursor(true), posend = cm.getDoc().getCursor(false); for (var i=pos.line; i<(posend.line+1);i++) { cm.replaceRange('* '+cm.getLine(i), { line: i, ch: 0 }, { line: i, ch: cm.getLine(i).length }); } cm.setCursor({ line: posend.line, ch: cm.getLine(posend.line).length }); cm.focus(); } }); editor.element.on('action.listOl', function() { if (editor.getCursorMode() == 'markdown') { var cm = editor.editor, pos = cm.getDoc().getCursor(true), posend = cm.getDoc().getCursor(false), prefix = 1; if (pos.line > 0) { var prevline = cm.getLine(pos.line-1), matches; if(matches = prevline.match(/^(\d+)\./)) { prefix = Number(matches[1])+1; } } for (var i=pos.line; i<(posend.line+1);i++) { cm.replaceRange(prefix+'. '+cm.getLine(i), { line: i, ch: 0 }, { line: i, ch: cm.getLine(i).length }); prefix++; } cm.setCursor({ line: posend.line, ch: cm.getLine(posend.line).length }); cm.focus(); } }); if (typeof window.customToolbarElements !== 'undefined') { window.customToolbarElements.forEach(function(customToolbarElement) { editor.element.on('action.' + customToolbarElement.identifier, function() { if (editor.getCursorMode() == 'markdown') { customToolbarElement.processAction(editor); } }); }); } editor.element.on('cursorMode', function(e, param) { if (editor.editor.options.mode == 'gfm') { var pos = editor.editor.getDoc().getCursor(); if (!editor.editor.getTokenAt(pos).state.base.htmlState) { param.mode = 'markdown'; } } }); $.extend(editor, { enableMarkdown: function() { enableMarkdown() this.render(); }, disableMarkdown: function() { this.editor.setOption('mode', 'htmlmixed'); this.mdeditor.find('.grav-mdeditor-button-code a').html(this.options.lblCodeview); this.render(); } }); // switch markdown mode on event editor.element.on({ enableMarkdown : function() { editor.enableMarkdown(); }, disableMarkdown : function() { editor.disableMarkdown(); } }); function enableMarkdown() { editor.editor.setOption('mode', 'gfm'); editor.mdeditor.find('.grav-mdeditor-button-code a').html(editor.options.lblMarkedview); } editor.mdeditor.on('click', 'a[data-mdeditor-button="fullscreen"]', function() { editor.mdeditor.toggleClass('grav-mdeditor-fullscreen'); var wrap = editor.editor.getWrapperElement(); if (editor.mdeditor.hasClass('grav-mdeditor-fullscreen')) { editor.editor.state.fullScreenRestore = {scrollTop: window.pageYOffset, scrollLeft: window.pageXOffset, width: wrap.style.width, height: wrap.style.height}; wrap.style.width = ''; wrap.style.height = editor.content.height()+'px'; document.documentElement.style.overflow = 'hidden'; } else { document.documentElement.style.overflow = ''; var info = editor.editor.state.fullScreenRestore; wrap.style.width = info.width; wrap.style.height = info.height; window.scrollTo(info.scrollLeft, info.scrollTop); } setTimeout(function() { editor.fit(); $(window).trigger('resize'); }, 50); }); editor.addShortcut(['Ctrl-S', 'Cmd-S'], function() { editor.element.trigger('mdeditor-save', [editor]); }); editor.addShortcutAction('bold', ['Ctrl-B', 'Cmd-B']); editor.addShortcutAction('italic', ['Ctrl-I', 'Cmd-I']); function addAction(name, replace, mode) { editor.element.on('action.'+name, function() { if (editor.getCursorMode() == 'markdown') { editor[mode == 'replaceLine' ? 'replaceLine' : 'replaceSelection'](replace, name); } }); } } // toolbar actions this._initToolbar($this); this._buildtoolbar(); } // init $(function(){ $('textarea[data-grav-mdeditor]').each(function() { var editor = $(this), obj; if (!editor.data('mdeditor')) { obj = MDEditor(editor, JSON.parse(editor.attr('data-grav-mdeditor') || '{}')); } }); }) })());