Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
cockpit/assets/lib/uikit/js/components/htmleditor.js /
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
679 lines (510 sloc)
25.3 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /*! UIkit 2.27.5 | 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.$('<iframe class="uk-htmleditor-iframe" frameborder="0" scrolling="auto" height="100" width="100%"></iframe>'); | |
| 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('<link rel="stylesheet" href="'+this.options.iframe+'">'); | |
| } | |
| } 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('<li><a data-htmleditor-button="'+button+'" title="'+title+'" data-uk-tooltip>'+$this.buttons[button].label+'</a></li>'); | |
| }); | |
| 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 = [ | |
| '<div class="uk-htmleditor uk-clearfix" data-mode="split">', | |
| '<div class="uk-htmleditor-navbar">', | |
| '<ul class="uk-htmleditor-navbar-nav uk-htmleditor-toolbar"></ul>', | |
| '<div class="uk-htmleditor-navbar-flip">', | |
| '<ul class="uk-htmleditor-navbar-nav">', | |
| '<li class="uk-htmleditor-button-code"><a>{:lblCodeview}</a></li>', | |
| '<li class="uk-htmleditor-button-preview"><a>{:lblPreview}</a></li>', | |
| '<li><a data-htmleditor-button="fullscreen"><i class="uk-icon-expand"></i></a></li>', | |
| '</ul>', | |
| '</div>', | |
| '</div>', | |
| '<div class="uk-htmleditor-content">', | |
| '<div class="uk-htmleditor-code"></div>', | |
| '<div class="uk-htmleditor-preview"><div></div></div>', | |
| '</div>', | |
| '</div>' | |
| ].join(''); | |
| UI.plugin('htmleditor', 'base', { | |
| init: function(editor) { | |
| editor.addButtons({ | |
| fullscreen: { | |
| title : 'Fullscreen', | |
| label : '<i class="uk-icon-expand"></i>' | |
| }, | |
| bold : { | |
| title : 'Bold', | |
| label : '<i class="uk-icon-bold"></i>' | |
| }, | |
| italic : { | |
| title : 'Italic', | |
| label : '<i class="uk-icon-italic"></i>' | |
| }, | |
| strike : { | |
| title : 'Strikethrough', | |
| label : '<i class="uk-icon-strikethrough"></i>' | |
| }, | |
| blockquote : { | |
| title : 'Blockquote', | |
| label : '<i class="uk-icon-quote-right"></i>' | |
| }, | |
| link : { | |
| title : 'Link', | |
| label : '<i class="uk-icon-link"></i>' | |
| }, | |
| image : { | |
| title : 'Image', | |
| label : '<i class="uk-icon-picture-o"></i>' | |
| }, | |
| listUl : { | |
| title : 'Unordered List', | |
| label : '<i class="uk-icon-list-ul"></i>' | |
| }, | |
| listOl : { | |
| title : 'Ordered List', | |
| label : '<i class="uk-icon-list-ol"></i>' | |
| } | |
| }); | |
| addAction('bold', '<strong>$1</strong>'); | |
| addAction('italic', '<em>$1</em>'); | |
| addAction('strike', '<del>$1</del>'); | |
| addAction('blockquote', '<blockquote><p>$1</p></blockquote>', 'replaceLine'); | |
| addAction('link', '<a href="http://">$1</a>'); | |
| addAction('image', '<img src="http://" alt="$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('<li>'+cm.getLine(i)+'</li>', { 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"+'</'+tag+'>', { 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', ''); | |
| 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; | |
| }); |