/*! UIkit 2.27.4 | http://www.getuikit.com | (c) 2014 YOOtheme | MIT License */ (function(addon) { var component; if (window.UIkit2) { component = addon(UIkit2); } if (typeof define == 'function' && define.amd) { define('uikit-htmleditor', ['uikit'], function(){ return component || addon(UIkit2); }); } })(function(UI) { "use strict"; var editors = []; UI.component('htmleditor', { defaults: { iframe : false, mode : 'split', markdown : false, autocomplete : true, enablescripts: false, height : 500, maxsplitsize : 1000, codemirror : { mode: 'htmlmixed', lineWrapping: true, dragDrop: false, autoCloseTags: true, matchTags: true, autoCloseBrackets: true, matchBrackets: true, indentUnit: 4, indentWithTabs: false, tabSize: 4, hintOptions: {completionSingle:false} }, toolbar : [ 'bold', 'italic', 'strike', 'link', 'image', 'blockquote', 'listUl', 'listOl' ], lblPreview : 'Preview', lblCodeview : 'HTML', lblMarkedview: 'Markdown' }, boot: function() { // init code UI.ready(function(context) { UI.$('textarea[data-uk-htmleditor]', context).each(function() { var editor = UI.$(this); if (!editor.data('htmleditor')) { UI.htmleditor(editor, UI.Utils.options(editor.attr('data-uk-htmleditor'))); } }); }); }, init: function() { var $this = this, tpl = UI.components.htmleditor.template; this.CodeMirror = this.options.CodeMirror || CodeMirror; this.buttons = {}; tpl = tpl.replace(/\{:lblPreview}/g, this.options.lblPreview); tpl = tpl.replace(/\{:lblCodeview}/g, this.options.lblCodeview); this.htmleditor = UI.$(tpl); this.content = this.htmleditor.find('.uk-htmleditor-content'); this.toolbar = this.htmleditor.find('.uk-htmleditor-toolbar'); this.preview = this.htmleditor.find('.uk-htmleditor-preview').children().eq(0); this.code = this.htmleditor.find('.uk-htmleditor-code'); this.element.before(this.htmleditor).appendTo(this.code); this.editor = this.CodeMirror.fromTextArea(this.element[0], this.options.codemirror); this.editor.htmleditor = this; this.editor.on('change', UI.Utils.debounce(function() { $this.render(); }, 150)); this.editor.on('change', function() { $this.editor.save(); $this.element.trigger('input'); }); this.code.find('.CodeMirror').css('height', this.options.height); // iframe mode? if (this.options.iframe) { this.iframe = UI.$(''); this.preview.append(this.iframe); // must open and close document object to start using it! this.iframe[0].contentWindow.document.open(); this.iframe[0].contentWindow.document.close(); this.preview.container = UI.$(this.iframe[0].contentWindow.document).find('body'); // append custom stylesheet if (typeof(this.options.iframe) === 'string') { this.preview.container.parent().append(''); } } else { this.preview.container = this.preview; } UI.$win.on('resize load', UI.Utils.debounce(function() { $this.fit(); }, 200)); var previewContainer = this.iframe ? this.preview.container:$this.preview.parent(), codeContent = this.code.find('.CodeMirror-sizer'), codeScroll = this.code.find('.CodeMirror-scroll').on('scroll', UI.Utils.debounce(function() { if ($this.htmleditor.attr('data-mode') == 'tab') return; // calc position var codeHeight = codeContent.height() - codeScroll.height(), previewHeight = previewContainer[0].scrollHeight - ($this.iframe ? $this.iframe.height() : previewContainer.height()), ratio = previewHeight / codeHeight, previewPosition = codeScroll.scrollTop() * ratio; // apply new scroll previewContainer.scrollTop(previewPosition); }, 10)); this.htmleditor.on('click', '.uk-htmleditor-button-code, .uk-htmleditor-button-preview', function(e) { e.preventDefault(); if ($this.htmleditor.attr('data-mode') == 'tab') { $this.htmleditor.find('.uk-htmleditor-button-code, .uk-htmleditor-button-preview').removeClass('uk-active').filter(this).addClass('uk-active'); $this.activetab = UI.$(this).hasClass('uk-htmleditor-button-code') ? 'code' : 'preview'; $this.htmleditor.attr('data-active-tab', $this.activetab); $this.editor.refresh(); } }); // toolbar actions this.htmleditor.on('click', 'a[data-htmleditor-button]', function() { if (!$this.code.is(':visible')) return; $this.trigger('action.' + UI.$(this).data('htmleditor-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', UI.Utils.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 = UI.Utils.debounce(function () { $this.redraw(); }, 5); this.on('init.uk.component', function() { $this.debouncedRedraw(); }); this.element.attr('data-uk-check-display', 1).on('display.uk.check', function(e) { if (this.htmleditor.is(":visible")) this.fit(); }.bind(this)); editors.push(this); }, addButton: function(name, button) { this.buttons[name] = button; }, addButtons: function(buttons) { UI.$.extend(this.buttons, buttons); }, replaceInPreview: function(regexp, callback) { var editor = this.editor, results = [], value = editor.getValue(), offset = -1, index = 0; this.currentvalue = this.currentvalue.replace(regexp, function() { offset = value.indexOf(arguments[0], ++offset); var match = { matches: arguments, from : translateOffset(offset), to : translateOffset(offset + arguments[0].length), replace: function(value) { editor.replaceRange(value, match.from, match.to); }, inRange: function(cursor) { if (cursor.line === match.from.line && cursor.line === match.to.line) { return cursor.ch >= match.from.ch && cursor.ch < match.to.ch; } return (cursor.line === match.from.line && cursor.ch >= match.from.ch) || (cursor.line > match.from.line && cursor.line < match.to.line) || (cursor.line === match.to.line && cursor.ch < match.to.ch); } }; var result = typeof(callback) === 'string' ? callback : callback(match, index); if (!result && result !== '') { return arguments[0]; } index++; results.push(match); return result; }); function translateOffset(offset) { var result = editor.getValue().substring(0, offset).split('\n'); return { line: result.length - 1, ch: result[result.length - 1].length } } return results; }, _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; bar.push('
  • '+$this.buttons[button].label+'
  • '); }); this.toolbar.html(bar.join('\n')); }, fit: function() { var mode = this.options.mode; if (mode == 'split' && this.htmleditor.width() < this.options.maxsplitsize) { mode = 'tab'; } if (mode == 'tab') { if (!this.activetab) { this.activetab = 'code'; this.htmleditor.attr('data-active-tab', this.activetab); } this.htmleditor.find('.uk-htmleditor-button-code, .uk-htmleditor-button-preview').removeClass('uk-active') .filter(this.activetab == 'code' ? '.uk-htmleditor-button-code' : '.uk-htmleditor-button-preview') .addClass('uk-active'); } this.editor.refresh(); this.preview.parent().css('height', this.code.height()); this.htmleditor.attr('data-mode', mode); }, redraw: function() { this._buildtoolbar(); this.render(); this.fit(); }, getMode: function() { return this.editor.getOption('mode'); }, getCursorMode: function() { var param = { mode: 'html'}; this.trigger('cursorMode', [param]); return param.mode; }, render: function() { this.currentvalue = this.editor.getValue(); if (!this.options.enablescripts) { this.currentvalue = this.currentvalue.replace(/<(script|style)\b[^<]*(?:(?!<\/(script|style)>)<[^<]*)*<\/(script|style)>/img, ''); } // empty code if (!this.currentvalue) { this.element.val(''); this.preview.container.html(''); return; } this.trigger('render', [this]); this.trigger('renderLate', [this]); this.preview.container.html(this.currentvalue); }, addShortcut: function(name, callback) { var map = {}; if (!UI.$.isArray(name)) { name = [name]; } name.forEach(function(key) { map[key] = callback; }); this.editor.addKeyMap(map); return map; }, addShortcutAction: function(action, shortcuts) { var editor = this; this.addShortcut(shortcuts, function() { editor.element.trigger('action.' + action, [editor.editor]); }); }, replaceSelection: function(replace) { var text = this.editor.getSelection(); if (!text.length) { var cur = this.editor.getCursor(), curLine = this.editor.getLine(cur.line), start = cur.ch, end = start; 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; } } var html = replace.replace('$1', text); this.editor.replaceSelection(html, 'end'); this.editor.focus(); }, replaceLine: function(replace) { 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(); }, save: function() { this.editor.save(); } }); UI.components.htmleditor.template = [ '
    ', '
    ', '', '
    ', '', '
    ', '
    ', '
    ', '
    ', '
    ', '
    ', '
    ' ].join(''); UI.plugin('htmleditor', 'base', { init: function(editor) { editor.addButtons({ 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 : '' } }); addAction('bold', '$1'); addAction('italic', '$1'); addAction('strike', '$1'); addAction('blockquote', '

    $1

    ', 'replaceLine'); addAction('link', '$1'); addAction('image', '$1'); var listfn = function(tag) { if (editor.getCursorMode() == 'html') { tag = tag || 'ul'; var cm = editor.editor, doc = cm.getDoc(), pos = doc.getCursor(true), posend = doc.getCursor(false), im = CodeMirror.innerMode(cm.getMode(), cm.getTokenAt(cm.getCursor()).state), inList = im && im.state && im.state.context && ['ul','ol'].indexOf(im.state.context.tagName) != -1; 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 }); } if (!inList) { cm.replaceRange('<'+tag+'>'+"\n"+cm.getLine(pos.line), { line: pos.line, ch: 0 }, { line: pos.line, ch: cm.getLine(pos.line).length }); cm.replaceRange(cm.getLine((posend.line+1))+"\n"+'', { line: (posend.line+1), ch: 0 }, { line: (posend.line+1), ch: cm.getLine((posend.line+1)).length }); cm.setCursor({ line: posend.line+1, ch: cm.getLine(posend.line+1).length }); } else { cm.setCursor({ line: posend.line, ch: cm.getLine(posend.line).length }); } cm.focus(); } }; editor.on('action.listUl', function() { listfn('ul'); }); editor.on('action.listOl', function() { listfn('ol'); }); editor.htmleditor.on('click', 'a[data-htmleditor-button="fullscreen"]', function() { editor.htmleditor.toggleClass('uk-htmleditor-fullscreen'); var wrap = editor.editor.getWrapperElement(); if (editor.htmleditor.hasClass('uk-htmleditor-fullscreen')) { var fixedParent = false, parents = editor.htmleditor.parents().each(function(){ if (UI.$(this).css('position')=='fixed' && !UI.$(this).is('html')) { fixedParent = UI.$(this); } }); editor.htmleditor.data('fixedParents', false); if (fixedParent) { var transformed = []; fixedParent = fixedParent.parent().find(parents).each(function(){ if (UI.$(this).css('transform') != 'none') { transformed.push(UI.$(this).data('transform-reset', { 'transform': this.style.transform, '-webkit-transform': this.style.webkitTransform, '-webkit-transition':this.style.webkitTransition, 'transition':this.style.transition }).css({ 'transform': 'none', '-webkit-transform': 'none', '-webkit-transition':'none', 'transition':'none' })); } }); editor.htmleditor.data('fixedParents', transformed); } 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); if (editor.htmleditor.data('fixedParents')) { editor.htmleditor.data('fixedParents').forEach(function(parent){ parent.css(parent.data('transform-reset')); }); } } setTimeout(function() { editor.fit(); UI.$win.trigger('resize'); }, 50); }); editor.addShortcut(['Ctrl-S', 'Cmd-S'], function() { editor.element.trigger('htmleditor-save', [editor]); }); editor.addShortcutAction('bold', ['Ctrl-B', 'Cmd-B']); function addAction(name, replace, mode) { editor.on('action.'+name, function() { if (editor.getCursorMode() == 'html') { editor[mode == 'replaceLine' ? 'replaceLine' : 'replaceSelection'](replace); } }); } } }); UI.plugin('htmleditor', 'markdown', { init: function(editor) { var parser = editor.options.mdparser || window.marked || null; if (!parser) return; if (editor.options.markdown) { enableMarkdown(); } addAction('bold', '**$1**'); addAction('italic', '*$1*'); addAction('strike', '~~$1~~'); addAction('blockquote', '> $1', 'replaceLine'); addAction('link', '[$1](http://)'); addAction('image', '![$1](http://)'); editor.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.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(); } }); editor.on('renderLate', function() { if (editor.editor.options.mode == 'gfm') { editor.currentvalue = parser(editor.currentvalue); } }); editor.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'; } } }); UI.$.extend(editor, { enableMarkdown: function() { enableMarkdown(); this.render(); }, disableMarkdown: function() { this.editor.setOption('mode', 'htmlmixed'); this.htmleditor.find('.uk-htmleditor-button-code a').html(this.options.lblCodeview); this.render(); } }); // switch markdown mode on event editor.on({ enableMarkdown : function() { editor.enableMarkdown(); }, disableMarkdown : function() { editor.disableMarkdown(); } }); function enableMarkdown() { editor.editor.setOption('mode', 'gfm'); editor.htmleditor.find('.uk-htmleditor-button-code a').html(editor.options.lblMarkedview); } function addAction(name, replace, mode) { editor.on('action.'+name, function() { if (editor.getCursorMode() == 'markdown') { editor[mode == 'replaceLine' ? 'replaceLine' : 'replaceSelection'](replace); } }); } } }); return UI.htmleditor; });