From c94fb32c7a3c28b18a27460aa2447eeec1fac1de Mon Sep 17 00:00:00 2001 From: Pascal Szewczyk Date: Mon, 18 Jul 2016 23:23:54 +0200 Subject: uikit added --- js/components/htmleditor.js | 679 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 679 insertions(+) create mode 100755 js/components/htmleditor.js (limited to 'js/components/htmleditor.js') diff --git a/js/components/htmleditor.js b/js/components/htmleditor.js new file mode 100755 index 0000000..68b5b52 --- /dev/null +++ b/js/components/htmleditor.js @@ -0,0 +1,679 @@ +/*! UIkit 2.26.4 | http://www.getuikit.com | (c) 2014 YOOtheme | MIT License */ +(function(addon) { + + var component; + + if (window.UIkit) { + component = addon(UIkit); + } + + if (typeof define == "function" && define.amd) { + define("uikit-htmleditor", ["uikit"], function(){ + return component || addon(UIkit); + }); + } + +})(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; +}); -- cgit v1.2.3