vendor/assets/javascripts/simditor/simditor.js in simditor-2.2.4 vs vendor/assets/javascripts/simditor/simditor.js in simditor-2.3.0

- old
+ new

@@ -1,9 +1,9 @@ /*! -* Simditor v2.2.3 +* Simditor v2.3.0 * http://simditor.tower.im/ -* 2015-08-22 +* 2015-10-08 */ (function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module unless amdModuleId is set define('simditor', ["jquery","simple-module","simple-hotkeys","simple-uploader"], function ($, SimpleModule, simpleHotkeys, simpleUploader) { @@ -17,11 +17,11 @@ } else { root['Simditor'] = factory(jQuery,SimpleModule,simple.hotkeys,simple.uploader); } }(this, function ($, SimpleModule, simpleHotkeys, simpleUploader) { -var AlignmentButton, BlockquoteButton, BoldButton, Button, CodeButton, CodePopover, ColorButton, Formatter, HrButton, ImageButton, ImagePopover, IndentButton, Indentation, InputManager, ItalicButton, Keystroke, LinkButton, LinkPopover, ListButton, OrderListButton, OutdentButton, Popover, Selection, Simditor, StrikethroughButton, TableButton, TitleButton, Toolbar, UnderlineButton, UndoManager, UnorderListButton, Util, +var AlignmentButton, BlockquoteButton, BoldButton, Button, Clipboard, CodeButton, CodePopover, ColorButton, Formatter, HrButton, ImageButton, ImagePopover, IndentButton, Indentation, InputManager, ItalicButton, Keystroke, LinkButton, LinkPopover, ListButton, OrderListButton, OutdentButton, Popover, Selection, Simditor, StrikethroughButton, TableButton, TitleButton, Toolbar, UnderlineButton, UndoManager, UnorderListButton, Util, extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, hasProp = {}.hasOwnProperty, indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, slice = [].slice; @@ -470,19 +470,24 @@ allowedStyles: {} }; Formatter.prototype._init = function() { this.editor = this._module; - this._allowedTags = $.merge(['br', 'span', 'a', 'img', 'b', 'strong', 'i', 'u', 'font', 'p', 'ul', 'ol', 'li', 'blockquote', 'pre', 'code', 'h1', 'h2', 'h3', 'h4', 'hr'], this.opts.allowedTags); + this._allowedTags = $.merge(['br', 'span', 'a', 'img', 'b', 'strong', 'i', 'strike', 'u', 'font', 'p', 'ul', 'ol', 'li', 'blockquote', 'pre', 'code', 'h1', 'h2', 'h3', 'h4', 'hr'], this.opts.allowedTags); this._allowedAttributes = $.extend({ img: ['src', 'alt', 'width', 'height', 'data-non-image'], a: ['href', 'target'], font: ['color'], code: ['class'] }, this.opts.allowedAttributes); this._allowedStyles = $.extend({ span: ['color'], + b: ['color'], + i: ['color'], + strong: ['color'], + strike: ['color'], + u: ['color'], p: ['margin-left', 'text-align'], h1: ['margin-left', 'text-align'], h2: ['margin-left', 'text-align'], h3: ['margin-left', 'text-align'], h4: ['margin-left', 'text-align'] @@ -756,20 +761,16 @@ return InputManager.__super__.constructor.apply(this, arguments); } InputManager.pluginName = 'InputManager'; - InputManager.prototype.opts = { - pasteImage: false - }; - InputManager.prototype._modifierKeys = [16, 17, 18, 91, 93, 224]; InputManager.prototype._arrowKeys = [37, 38, 39, 40]; InputManager.prototype._init = function() { - var submitKey; + var selectAllKey, submitKey; this.editor = this._module; this.throttledValueChanged = this.editor.util.throttle((function(_this) { return function(params) { return setTimeout(function() { return _this.editor.trigger('valuechanged', params); @@ -779,40 +780,43 @@ this.throttledSelectionChanged = this.editor.util.throttle((function(_this) { return function() { return _this.editor.trigger('selectionchanged'); }; })(this), 50); - if (this.opts.pasteImage && typeof this.opts.pasteImage !== 'string') { - this.opts.pasteImage = 'inline'; - } - this._keystrokeHandlers = {}; - this.hotkeys = simpleHotkeys({ - el: this.editor.body - }); - this._pasteArea = $('<div/>').css({ - width: '1px', - height: '1px', - overflow: 'hidden', - position: 'fixed', - right: '0', - bottom: '100px' - }).attr({ - tabIndex: '-1', - contentEditable: true - }).addClass('simditor-paste-area').appendTo(this.editor.el); $(document).on('selectionchange.simditor' + this.editor.id, (function(_this) { return function(e) { - if (!_this.focused) { + var triggerEvent; + if (!(_this.focused && !_this.editor.clipboard.pasting)) { return; } - return _this.throttledSelectionChanged(); + triggerEvent = function() { + if (_this._selectionTimer) { + clearTimeout(_this._selectionTimer); + _this._selectionTimer = null; + } + if (_this.editor.selection._selection.rangeCount > 0) { + return _this.throttledSelectionChanged(); + } else { + return _this._selectionTimer = setTimeout(function() { + _this._selectionTimer = null; + if (_this.focused) { + return triggerEvent(); + } + }, 10); + } + }; + return triggerEvent(); }; })(this)); this.editor.on('valuechanged', (function(_this) { return function() { + var $rootBlocks; _this.lastCaretPosition = null; - if (_this.focused && !_this.editor.selection.blockNodes().length) { + $rootBlocks = _this.editor.body.children().filter(function(i, node) { + return _this.editor.util.isBlockNode(node); + }); + if (_this.focused && $rootBlocks.length === 0) { _this.editor.selection.save(); _this.editor.formatter.format(); _this.editor.selection.restore(); } _this.editor.body.find('hr, pre, .simditor-table').each(function(i, el) { @@ -837,27 +841,28 @@ if (!_this.editor.util.support.onselectionchange && _this.focused) { return _this.throttledSelectionChanged(); } }; })(this)); - this.editor.body.on('keydown', $.proxy(this._onKeyDown, this)).on('keypress', $.proxy(this._onKeyPress, this)).on('keyup', $.proxy(this._onKeyUp, this)).on('mouseup', $.proxy(this._onMouseUp, this)).on('focus', $.proxy(this._onFocus, this)).on('blur', $.proxy(this._onBlur, this)).on('paste', $.proxy(this._onPaste, this)).on('drop', $.proxy(this._onDrop, this)).on('input', $.proxy(this._onInput, this)); + this.editor.body.on('keydown', $.proxy(this._onKeyDown, this)).on('keypress', $.proxy(this._onKeyPress, this)).on('keyup', $.proxy(this._onKeyUp, this)).on('mouseup', $.proxy(this._onMouseUp, this)).on('focus', $.proxy(this._onFocus, this)).on('blur', $.proxy(this._onBlur, this)).on('drop', $.proxy(this._onDrop, this)).on('input', $.proxy(this._onInput, this)); if (this.editor.util.browser.firefox) { - this.addShortcut('cmd+left', (function(_this) { + this.editor.hotkeys.add('cmd+left', (function(_this) { return function(e) { e.preventDefault(); _this.editor.selection._selection.modify('move', 'backward', 'lineboundary'); return false; }; })(this)); - this.addShortcut('cmd+right', (function(_this) { + this.editor.hotkeys.add('cmd+right', (function(_this) { return function(e) { e.preventDefault(); _this.editor.selection._selection.modify('move', 'forward', 'lineboundary'); return false; }; })(this)); - this.addShortcut((this.editor.util.os.mac ? 'cmd+a' : 'ctrl+a'), (function(_this) { + selectAllKey = this.editor.util.os.mac ? 'cmd+a' : 'ctrl+a'; + this.editor.hotkeys.add(selectAllKey, (function(_this) { return function(e) { var $children, firstBlock, lastBlock, range; $children = _this.editor.body.children(); if (!($children.length > 0)) { return; @@ -871,11 +876,11 @@ return false; }; })(this)); } submitKey = this.editor.util.os.mac ? 'cmd+enter' : 'ctrl+enter'; - this.addShortcut(submitKey, (function(_this) { + this.editor.hotkeys.add(submitKey, (function(_this) { return function(e) { _this.editor.el.closest('form').find('button:submit').click(); return false; }; })(this)); @@ -887,10 +892,13 @@ })(this), 0); } }; InputManager.prototype._onFocus = function(e) { + if (this.editor.clipboard.pasting) { + return; + } this.editor.el.addClass('focus').removeClass('error'); this.focused = true; this.lastCaretPosition = null; return setTimeout((function(_this) { return function() { @@ -902,10 +910,13 @@ })(this), 0); }; InputManager.prototype._onBlur = function(e) { var ref; + if (this.editor.clipboard.pasting) { + return; + } this.editor.el.removeClass('focus'); this.editor.sync(); this.focused = false; this.lastCaretPosition = (ref = this.editor.undoManager.currentState()) != null ? ref.caret : void 0; return this.editor.triggerHandler('blur'); @@ -916,40 +927,20 @@ return this.throttledSelectionChanged(); } }; InputManager.prototype._onKeyDown = function(e) { - var base, ref, ref1, result; + var ref, ref1; if (this.editor.triggerHandler(e) === false) { return false; } - if (this.hotkeys.respondTo(e)) { + if (this.editor.hotkeys.respondTo(e)) { return; } - if (e.which in this._keystrokeHandlers) { - result = typeof (base = this._keystrokeHandlers[e.which])['*'] === "function" ? base['*'](e) : void 0; - if (result) { - this.throttledValueChanged(); - return false; - } - this.editor.selection.startNodes().each((function(_this) { - return function(i, node) { - var handler, ref; - if (node.nodeType !== Node.ELEMENT_NODE) { - return; - } - handler = (ref = _this._keystrokeHandlers[e.which]) != null ? ref[node.tagName.toLowerCase()] : void 0; - result = typeof handler === "function" ? handler(e, $(node)) : void 0; - if (result === true || result === false) { - return false; - } - }; - })(this)); - if (result) { - this.throttledValueChanged(); - return false; - } + if (this.editor.keystroke.respondTo(e)) { + this.throttledValueChanged(); + return false; } if ((ref = e.which, indexOf.call(this._modifierKeys, ref) >= 0) || (ref1 = e.which, indexOf.call(this._arrowKeys, ref1) >= 0)) { return; } if (this.editor.util.metaKey(e) && e.which === 86) { @@ -981,173 +972,10 @@ p = $('<p/>').append(this.editor.util.phBr).appendTo(this.editor.body); this.editor.selection.setRangeAtStartOf(p); } }; - InputManager.prototype._onPaste = function(e) { - var $blockEl, cleanPaste, imageFile, pasteContent, pasteItem, processPasteContent, range, ref, uploadOpt; - if (this.editor.triggerHandler(e) === false) { - return false; - } - range = this.editor.selection.deleteRangeContents(); - if (!range.collapsed) { - range.collapse(true); - } - this.editor.selection.range(range); - $blockEl = this.editor.selection.blockNodes().last(); - cleanPaste = $blockEl.is('pre, table'); - if (e.originalEvent.clipboardData && e.originalEvent.clipboardData.items && e.originalEvent.clipboardData.items.length > 0) { - pasteItem = e.originalEvent.clipboardData.items[0]; - if (/^image\//.test(pasteItem.type) && !cleanPaste) { - imageFile = pasteItem.getAsFile(); - if (!((imageFile != null) && this.opts.pasteImage)) { - return; - } - if (!imageFile.name) { - imageFile.name = "Clipboard Image.png"; - } - uploadOpt = {}; - uploadOpt[this.opts.pasteImage] = true; - if ((ref = this.editor.uploader) != null) { - ref.upload(imageFile, uploadOpt); - } - return false; - } - } - processPasteContent = (function(_this) { - return function(pasteContent) { - var $img, blob, children, insertPosition, k, l, lastLine, len, len1, len2, len3, len4, line, lines, m, node, o, q, ref1, ref2, ref3; - if (_this.editor.triggerHandler('pasting', [pasteContent]) === false) { - return; - } - if (!pasteContent) { - return; - } else if (cleanPaste) { - if ($blockEl.is('table')) { - lines = pasteContent.split('\n'); - lastLine = lines.pop(); - for (k = 0, len = lines.length; k < len; k++) { - line = lines[k]; - _this.editor.selection.insertNode(document.createTextNode(line)); - _this.editor.selection.insertNode($('<br/>')); - } - _this.editor.selection.insertNode(document.createTextNode(lastLine)); - } else { - pasteContent = $('<div/>').text(pasteContent); - ref1 = pasteContent.contents(); - for (l = 0, len1 = ref1.length; l < len1; l++) { - node = ref1[l]; - _this.editor.selection.insertNode($(node)[0], range); - } - } - } else if ($blockEl.is(_this.editor.body)) { - for (m = 0, len2 = pasteContent.length; m < len2; m++) { - node = pasteContent[m]; - _this.editor.selection.insertNode(node, range); - } - } else if (pasteContent.length < 1) { - return; - } else if (pasteContent.length === 1) { - if (pasteContent.is('p')) { - children = pasteContent.contents(); - if (children.length === 1 && children.is('img')) { - $img = children; - if (/^data:image/.test($img.attr('src'))) { - if (!_this.opts.pasteImage) { - return; - } - blob = _this.editor.util.dataURLtoBlob($img.attr("src")); - blob.name = "Clipboard Image.png"; - uploadOpt = {}; - uploadOpt[_this.opts.pasteImage] = true; - if ((ref2 = _this.editor.uploader) != null) { - ref2.upload(blob, uploadOpt); - } - return; - } else if ($img.is('img[src^="webkit-fake-url://"]')) { - return; - } - } - for (o = 0, len3 = children.length; o < len3; o++) { - node = children[o]; - _this.editor.selection.insertNode(node, range); - } - } else if ($blockEl.is('p') && _this.editor.util.isEmptyNode($blockEl)) { - $blockEl.replaceWith(pasteContent); - _this.editor.selection.setRangeAtEndOf(pasteContent, range); - } else if (pasteContent.is('ul, ol')) { - if (pasteContent.find('li').length === 1) { - pasteContent = $('<div/>').text(pasteContent.text()); - ref3 = pasteContent.contents(); - for (q = 0, len4 = ref3.length; q < len4; q++) { - node = ref3[q]; - _this.editor.selection.insertNode($(node)[0], range); - } - } else if ($blockEl.is('li')) { - $blockEl.parent().after(pasteContent); - _this.editor.selection.setRangeAtEndOf(pasteContent, range); - } else { - $blockEl.after(pasteContent); - _this.editor.selection.setRangeAtEndOf(pasteContent, range); - } - } else { - $blockEl.after(pasteContent); - _this.editor.selection.setRangeAtEndOf(pasteContent, range); - } - } else { - if ($blockEl.is('li')) { - $blockEl = $blockEl.parent(); - } - if (_this.editor.selection.rangeAtStartOf($blockEl, range)) { - insertPosition = 'before'; - } else if (_this.editor.selection.rangeAtEndOf($blockEl, range)) { - insertPosition = 'after'; - } else { - _this.editor.selection.breakBlockEl($blockEl, range); - insertPosition = 'before'; - } - $blockEl[insertPosition](pasteContent); - _this.editor.selection.setRangeAtEndOf(pasteContent.last(), range); - } - return _this.throttledValueChanged(); - }; - })(this); - if (cleanPaste) { - e.preventDefault(); - if (this.editor.util.browser.msie) { - pasteContent = window.clipboardData.getData('Text'); - } else { - pasteContent = e.originalEvent.clipboardData.getData('text/plain'); - } - return processPasteContent(pasteContent); - } else { - this.editor.selection.save(range); - this._pasteArea.focus(); - if (this.editor.util.browser.msie && this.editor.util.browser.version === 10) { - e.preventDefault(); - this._pasteArea.html(window.clipboardData.getData('Text')); - } - return setTimeout((function(_this) { - return function() { - if (_this._pasteArea.is(':empty')) { - pasteContent = null; - } else { - pasteContent = $('<div/>').append(_this._pasteArea.contents()); - pasteContent.find('table colgroup').remove(); - _this.editor.formatter.format(pasteContent); - _this.editor.formatter.decorate(pasteContent); - _this.editor.formatter.beautify(pasteContent.children()); - pasteContent = pasteContent.contents(); - } - _this._pasteArea.empty(); - range = _this.editor.selection.restore(); - return processPasteContent(pasteContent); - }; - })(this), 10); - } - }; - InputManager.prototype._onDrop = function(e) { if (this.editor.triggerHandler(e) === false) { return false; } return this.throttledValueChanged(); @@ -1155,21 +983,10 @@ InputManager.prototype._onInput = function(e) { return this.throttledValueChanged(['oninput']); }; - InputManager.prototype.addKeystrokeHandler = function(key, node, handler) { - if (!this._keystrokeHandlers[key]) { - this._keystrokeHandlers[key] = {}; - } - return this._keystrokeHandlers[key][node] = handler; - }; - - InputManager.prototype.addShortcut = function(keys, handler) { - return this.hotkeys.add(keys, $.proxy(handler, this)); - }; - return InputManager; })(SimpleModule); Keystroke = (function(superClass) { @@ -1180,14 +997,57 @@ } Keystroke.pluginName = 'Keystroke'; Keystroke.prototype._init = function() { - var titleEnterHandler; this.editor = this._module; + this._keystrokeHandlers = {}; + return this._initKeystrokeHandlers(); + }; + + Keystroke.prototype.add = function(key, node, handler) { + key = key.toLowerCase(); + key = this.editor.hotkeys.constructor.aliases[key] || key; + if (!this._keystrokeHandlers[key]) { + this._keystrokeHandlers[key] = {}; + } + return this._keystrokeHandlers[key][node] = handler; + }; + + Keystroke.prototype.respondTo = function(e) { + var base, key, ref, result; + key = (ref = this.editor.hotkeys.constructor.keyNameMap[e.which]) != null ? ref.toLowerCase() : void 0; + if (!key) { + return; + } + if (key in this._keystrokeHandlers) { + result = typeof (base = this._keystrokeHandlers[key])['*'] === "function" ? base['*'](e) : void 0; + if (!result) { + this.editor.selection.startNodes().each((function(_this) { + return function(i, node) { + var handler, ref1; + if (node.nodeType !== Node.ELEMENT_NODE) { + return; + } + handler = (ref1 = _this._keystrokeHandlers[key]) != null ? ref1[node.tagName.toLowerCase()] : void 0; + result = typeof handler === "function" ? handler(e, $(node)) : void 0; + if (result === true || result === false) { + return false; + } + }; + })(this)); + } + if (result) { + return true; + } + } + }; + + Keystroke.prototype._initKeystrokeHandlers = function() { + var titleEnterHandler; if (this.editor.util.browser.safari) { - this.editor.inputManager.addKeystrokeHandler('13', '*', (function(_this) { + this.add('enter', '*', (function(_this) { return function(e) { var $blockEl, $br; if (!e.shiftKey) { return; } @@ -1217,18 +1077,18 @@ $p = $('<p/>').append(_this.editor.util.phBr).insertAfter($node); _this.editor.selection.setRangeAtStartOf($p); return true; }; })(this); - this.editor.inputManager.addKeystrokeHandler('13', 'h1', titleEnterHandler); - this.editor.inputManager.addKeystrokeHandler('13', 'h2', titleEnterHandler); - this.editor.inputManager.addKeystrokeHandler('13', 'h3', titleEnterHandler); - this.editor.inputManager.addKeystrokeHandler('13', 'h4', titleEnterHandler); - this.editor.inputManager.addKeystrokeHandler('13', 'h5', titleEnterHandler); - this.editor.inputManager.addKeystrokeHandler('13', 'h6', titleEnterHandler); + this.add('enter', 'h1', titleEnterHandler); + this.add('enter', 'h2', titleEnterHandler); + this.add('enter', 'h3', titleEnterHandler); + this.add('enter', 'h4', titleEnterHandler); + this.add('enter', 'h5', titleEnterHandler); + this.add('enter', 'h6', titleEnterHandler); } - this.editor.inputManager.addKeystrokeHandler('8', '*', (function(_this) { + this.add('backspace', '*', (function(_this) { return function(e) { var $blockEl, $prevBlockEl, $rootBlock, isWebkit; $rootBlock = _this.editor.selection.rootNodes().first(); $prevBlockEl = $rootBlock.prev(); if ($prevBlockEl.is('hr') && _this.editor.selection.rangeAtStartOf($rootBlock)) { @@ -1245,11 +1105,11 @@ _this.editor.selection.restore(); return null; } }; })(this)); - this.editor.inputManager.addKeystrokeHandler('13', 'li', (function(_this) { + this.add('enter', 'li', (function(_this) { return function(e, $node) { var $cloneNode, listEl, newBlockEl, newListEl; $cloneNode = $node.clone(); $cloneNode.find('ul, ol').remove(); if (!(_this.editor.util.isEmptyNode($cloneNode) && $node.is(_this.editor.selection.blockNodes().last()))) { @@ -1291,11 +1151,11 @@ } _this.editor.selection.setRangeAtStartOf(newBlockEl); return true; }; })(this)); - this.editor.inputManager.addKeystrokeHandler('13', 'pre', (function(_this) { + this.add('enter', 'pre', (function(_this) { return function(e, $node) { var $p, breakNode, range; e.preventDefault(); if (e.shiftKey) { $p = $('<p/>').append(_this.editor.util.phBr).insertAfter($node); @@ -1317,11 +1177,11 @@ range.collapse(false); _this.editor.selection.range(range); return true; }; })(this)); - this.editor.inputManager.addKeystrokeHandler('13', 'blockquote', (function(_this) { + this.add('enter', 'blockquote', (function(_this) { return function(e, $node) { var $closestBlock, range; $closestBlock = _this.editor.selection.blockNodes().last(); if (!($closestBlock.is('p') && !$closestBlock.next().length && _this.editor.util.isEmptyNode($closestBlock))) { return; @@ -1330,11 +1190,11 @@ range = document.createRange(); _this.editor.selection.setRangeAtStartOf($closestBlock, range); return true; }; })(this)); - this.editor.inputManager.addKeystrokeHandler('8', 'li', (function(_this) { + this.add('backspace', 'li', (function(_this) { return function(e, $node) { var $br, $childList, $newLi, $prevChildList, $prevNode, $textNode, isFF, range, text; $childList = $node.children('ul, ol'); $prevNode = $node.prev('li'); if (!($childList.length > 0 && $prevNode.length > 0)) { @@ -1379,11 +1239,11 @@ _this.editor.selection.range(range); } return true; }; })(this)); - this.editor.inputManager.addKeystrokeHandler('8', 'pre', (function(_this) { + this.add('backspace', 'pre', (function(_this) { return function(e, $node) { var $newNode, codeStr, range; if (!_this.editor.selection.rangeAtStartOf($node)) { return; } @@ -1393,11 +1253,11 @@ range = document.createRange(); _this.editor.selection.setRangeAtStartOf($newNode, range); return true; }; })(this)); - return this.editor.inputManager.addKeystrokeHandler('8', 'blockquote', (function(_this) { + return this.add('backspace', 'blockquote', (function(_this) { return function(e, $node) { var $firstChild, range; if (!_this.editor.selection.rangeAtStartOf($node)) { return; } @@ -1429,11 +1289,11 @@ UndoManager.prototype._startPosition = null; UndoManager.prototype._endPosition = null; UndoManager.prototype._init = function() { - var redoShortcut, throttledPushState, undoShortcut; + var redoShortcut, undoShortcut; this.editor = this._module; this._stack = []; if (this.editor.util.os.mac) { undoShortcut = 'cmd+z'; redoShortcut = 'shift+cmd+z'; @@ -1442,35 +1302,37 @@ redoShortcut = 'ctrl+y'; } else { undoShortcut = 'ctrl+z'; redoShortcut = 'shift+ctrl+z'; } - this.editor.inputManager.addShortcut(undoShortcut, (function(_this) { + this.editor.hotkeys.add(undoShortcut, (function(_this) { return function(e) { e.preventDefault(); _this.undo(); return false; }; })(this)); - this.editor.inputManager.addShortcut(redoShortcut, (function(_this) { + this.editor.hotkeys.add(redoShortcut, (function(_this) { return function(e) { e.preventDefault(); _this.redo(); return false; }; })(this)); - throttledPushState = this.editor.util.throttle((function(_this) { + this.throttledPushState = this.editor.util.throttle((function(_this) { return function() { return _this._pushUndoState(); }; })(this), 500); - this.editor.on('valuechanged', function(e, src) { - if (src === 'undo' || src === 'redo') { - return; - } - return throttledPushState(); - }); + this.editor.on('valuechanged', (function(_this) { + return function(e, src) { + if (src === 'undo' || src === 'redo') { + return; + } + return _this.throttledPushState(); + }; + })(this)); this.editor.on('selectionchanged', (function(_this) { return function(e) { _this._startPosition = null; _this._endPosition = null; return _this.update(); @@ -1567,47 +1429,44 @@ return this.editor.trigger('valuechanged', ['redo']); }; UndoManager.prototype.update = function() { var currentState, html; - if (this._timer) { - return; - } currentState = this.currentState(); if (!currentState) { return; } html = this.editor.body.html(); - if (html !== currentState.html) { - return; - } currentState.html = html; return currentState.caret = this.caretPosition(); }; UndoManager.prototype._getNodeOffset = function(node, index) { var $parent, merging, offset; - if (index) { + if ($.isNumeric(index)) { $parent = $(node); } else { $parent = $(node).parent(); } offset = 0; merging = false; $parent.contents().each(function(i, child) { - if (index === i || node === child) { + if (node === child || (index === i && i === 0)) { return false; } - if (child.nodeType === 3) { + if (child.nodeType === Node.TEXT_NODE) { if (!merging) { offset += 1; merging = true; } } else { offset += 1; merging = false; } + if (index - 1 === i) { + return false; + } return null; }); return offset; }; @@ -1617,20 +1476,23 @@ type = 'start'; } range = this.editor.selection.range(); offset = range[type + "Offset"]; $nodes = this.editor.selection[type + "Nodes"](); - if ((node = $nodes.first()[0]).nodeType === Node.TEXT_NODE) { + node = $nodes.first()[0]; + if (node.nodeType === Node.TEXT_NODE) { prevNode = node.previousSibling; while (prevNode && prevNode.nodeType === Node.TEXT_NODE) { node = prevNode; offset += this.editor.util.getNodeLength(prevNode); prevNode = prevNode.previousSibling; } nodes = $nodes.get(); nodes[0] = node; $nodes = $(nodes); + } else { + offset = this._getNodeOffset(node, offset); } position = [offset]; $nodes.each((function(_this) { return function(i, node) { return position.unshift(_this._getNodeOffset(node)); @@ -1645,11 +1507,11 @@ ref = position.slice(0, position.length - 1); for (i = k = 0, len = ref.length; k < len; i = ++k) { offset = ref[i]; childNodes = node.childNodes; if (offset > childNodes.length - 1) { - if (i === position.length - 2 && $(node).is('pre')) { + if (i === position.length - 2 && $(node).is('pre:empty')) { child = document.createTextNode(''); node.appendChild(child); childNodes = node.childNodes; } else { node = null; @@ -1734,38 +1596,45 @@ } return os; })(); Util.prototype.browser = (function() { - var chrome, firefox, ie, ref, ref1, ref2, ref3, safari, ua; + var chrome, edge, firefox, ie, ref, ref1, ref2, ref3, ref4, safari, ua; ua = navigator.userAgent; ie = /(msie|trident)/i.test(ua); chrome = /chrome|crios/i.test(ua); safari = /safari/i.test(ua) && !chrome; firefox = /firefox/i.test(ua); + edge = /edge/i.test(ua); if (ie) { return { msie: true, version: ((ref = ua.match(/(msie |rv:)(\d+(\.\d+)?)/i)) != null ? ref[2] : void 0) * 1 }; + } else if (edge) { + return { + edge: true, + webkit: true, + version: ((ref1 = ua.match(/edge\/(\d+(\.\d+)?)/i)) != null ? ref1[1] : void 0) * 1 + }; } else if (chrome) { return { webkit: true, chrome: true, - version: ((ref1 = ua.match(/(?:chrome|crios)\/(\d+(\.\d+)?)/i)) != null ? ref1[1] : void 0) * 1 + version: ((ref2 = ua.match(/(?:chrome|crios)\/(\d+(\.\d+)?)/i)) != null ? ref2[1] : void 0) * 1 }; } else if (safari) { return { webkit: true, safari: true, - version: ((ref2 = ua.match(/version\/(\d+(\.\d+)?)/i)) != null ? ref2[1] : void 0) * 1 + version: ((ref3 = ua.match(/version\/(\d+(\.\d+)?)/i)) != null ? ref3[1] : void 0) * 1 }; } else if (firefox) { return { mozilla: true, firefox: true, - version: ((ref3 = ua.match(/firefox\/(\d+(\.\d+)?)/i)) != null ? ref3[1] : void 0) * 1 + version: ((ref4 = ua.match(/firefox\/(\d+(\.\d+)?)/i)) != null ? ref4[1] : void 0) * 1 }; } else { return {}; } })(); @@ -1901,11 +1770,11 @@ last = +new Date(); rtn = func.apply(ctx, args); ctx = null; return args = null; }; - return throttled = function() { + throttled = function() { var delta; ctx = this; args = arguments; delta = new Date() - last; if (!timeoutID) { @@ -1915,10 +1784,18 @@ timeoutID = setTimeout(call, wait - delta); } } return rtn; }; + throttled.clear = function() { + if (!timeoutID) { + return; + } + clearTimeout(timeoutID); + return call(); + }; + return throttled; }; Util.prototype.formatHTML = function(html) { var cursor, indentString, lastMatch, level, match, re, repeatString, result, str; re = /<(\/?)(.+?)(\/?)>/g; @@ -2117,11 +1994,11 @@ tabIndent: true }; Indentation.prototype._init = function() { this.editor = this._module; - return this.editor.inputManager.addKeystrokeHandler('9', '*', (function(_this) { + return this.editor.keystroke.add('tab', '*', (function(_this) { return function(e) { var codeButton; codeButton = _this.editor.toolbar.findButton('code'); if (!(_this.opts.tabIndent || (codeButton && codeButton.active))) { return; @@ -2178,11 +2055,11 @@ if ($blockEl.is('pre')) { $pre = this.editor.selection.containerNode(); if (!($pre.is($blockEl) || $pre.closest('pre').is($blockEl))) { return; } - this.indentText(range); + this.indentText(this.editor.selection.range()); } else if ($blockEl.is('li')) { $parentLi = $blockEl.prev('li'); if ($parentLi.length < 1) { return; } @@ -2296,10 +2173,221 @@ return Indentation; })(SimpleModule); +Clipboard = (function(superClass) { + extend(Clipboard, superClass); + + function Clipboard() { + return Clipboard.__super__.constructor.apply(this, arguments); + } + + Clipboard.pluginName = 'Clipboard'; + + Clipboard.prototype.opts = { + pasteImage: false + }; + + Clipboard.prototype._init = function() { + this.editor = this._module; + if (this.opts.pasteImage && typeof this.opts.pasteImage !== 'string') { + this.opts.pasteImage = 'inline'; + } + return this.editor.body.on('paste', (function(_this) { + return function(e) { + var $blockEl, isPlainText, range; + if (_this.editor.triggerHandler(e) === false) { + return false; + } + range = _this.editor.selection.deleteRangeContents(); + if (!range.collapsed) { + range.collapse(true); + } + _this.editor.selection.range(range); + $blockEl = _this.editor.selection.blockNodes().last(); + isPlainText = $blockEl.is('pre, table'); + if (!isPlainText && _this._processPasteByClipboardApi(e)) { + return false; + } + if (_this._pasteBin) { + return false; + } + _this.editor.inputManager.throttledValueChanged.clear(); + _this.editor.inputManager.throttledSelectionChanged.clear(); + _this.editor.undoManager.throttledPushState.clear(); + _this.pasting = true; + return _this._getPasteContent(isPlainText, function(pasteContent) { + _this._processPasteContent(isPlainText, $blockEl, pasteContent); + return _this.pasting = false; + }); + }; + })(this)); + }; + + Clipboard.prototype._processPasteByClipboardApi = function(e) { + var imageFile, pasteItem, ref, uploadOpt; + if (this.editor.util.browser.edge) { + return; + } + if (e.originalEvent.clipboardData && e.originalEvent.clipboardData.items && e.originalEvent.clipboardData.items.length > 0) { + pasteItem = e.originalEvent.clipboardData.items[0]; + if (/^image\//.test(pasteItem.type)) { + imageFile = pasteItem.getAsFile(); + if (!((imageFile != null) && this.opts.pasteImage)) { + return; + } + if (!imageFile.name) { + imageFile.name = "Clipboard Image.png"; + } + if (this.editor.triggerHandler('pasting', [imageFile]) === false) { + return; + } + uploadOpt = {}; + uploadOpt[this.opts.pasteImage] = true; + if ((ref = this.editor.uploader) != null) { + ref.upload(imageFile, uploadOpt); + } + return true; + } + } + }; + + Clipboard.prototype._getPasteContent = function(isPlainText, callback) { + var state; + this._pasteBin = $('<div contenteditable="true" />').addClass('simditor-paste-bin').attr('tabIndex', '-1').appendTo(this.editor.el); + state = { + html: this.editor.body.html(), + caret: this.editor.undoManager.caretPosition() + }; + this._pasteBin.focus(); + return setTimeout((function(_this) { + return function() { + var pasteContent; + _this.editor.hidePopover(); + _this.editor.body.html(state.html); + _this.editor.undoManager.caretPosition(state.caret); + _this.editor.body.focus(); + _this.editor.selection._reset(); + if (isPlainText) { + pasteContent = _this.editor.formatter.clearHtml(_this._pasteBin.html(), true); + } else { + pasteContent = $('<div/>').append(_this._pasteBin.contents()); + pasteContent.find('table colgroup').remove(); + _this.editor.formatter.format(pasteContent); + _this.editor.formatter.decorate(pasteContent); + _this.editor.formatter.beautify(pasteContent.children()); + pasteContent = pasteContent.contents(); + } + _this._pasteBin.remove(); + _this._pasteBin = null; + return callback(pasteContent); + }; + })(this), 0); + }; + + Clipboard.prototype._processPasteContent = function(isPlainText, $blockEl, pasteContent) { + var $img, blob, children, insertPosition, k, l, lastLine, len, len1, len2, len3, len4, line, lines, m, node, o, q, ref, ref1, ref2, uploadOpt; + if (this.editor.triggerHandler('pasting', [pasteContent]) === false) { + return; + } + if (!pasteContent) { + return; + } else if (isPlainText) { + if ($blockEl.is('table')) { + lines = pasteContent.split('\n'); + lastLine = lines.pop(); + for (k = 0, len = lines.length; k < len; k++) { + line = lines[k]; + this.editor.selection.insertNode(document.createTextNode(line)); + this.editor.selection.insertNode($('<br/>')); + } + this.editor.selection.insertNode(document.createTextNode(lastLine)); + } else { + pasteContent = $('<div/>').text(pasteContent); + ref = pasteContent.contents(); + for (l = 0, len1 = ref.length; l < len1; l++) { + node = ref[l]; + this.editor.selection.insertNode($(node)[0]); + } + } + } else if ($blockEl.is(this.editor.body)) { + for (m = 0, len2 = pasteContent.length; m < len2; m++) { + node = pasteContent[m]; + this.editor.selection.insertNode(node); + } + } else if (pasteContent.length < 1) { + return; + } else if (pasteContent.length === 1) { + if (pasteContent.is('p')) { + children = pasteContent.contents(); + if (children.length === 1 && children.is('img')) { + $img = children; + if (/^data:image/.test($img.attr('src'))) { + if (!this.opts.pasteImage) { + return; + } + blob = this.editor.util.dataURLtoBlob($img.attr("src")); + blob.name = "Clipboard Image.png"; + uploadOpt = {}; + uploadOpt[this.opts.pasteImage] = true; + if ((ref1 = this.editor.uploader) != null) { + ref1.upload(blob, uploadOpt); + } + return; + } else if ($img.is('img[src^="webkit-fake-url://"]')) { + return; + } + } + for (o = 0, len3 = children.length; o < len3; o++) { + node = children[o]; + this.editor.selection.insertNode(node); + } + } else if ($blockEl.is('p') && this.editor.util.isEmptyNode($blockEl)) { + $blockEl.replaceWith(pasteContent); + this.editor.selection.setRangeAtEndOf(pasteContent); + } else if (pasteContent.is('ul, ol')) { + if (pasteContent.find('li').length === 1) { + pasteContent = $('<div/>').text(pasteContent.text()); + ref2 = pasteContent.contents(); + for (q = 0, len4 = ref2.length; q < len4; q++) { + node = ref2[q]; + this.editor.selection.insertNode($(node)[0]); + } + } else if ($blockEl.is('li')) { + $blockEl.parent().after(pasteContent); + this.editor.selection.setRangeAtEndOf(pasteContent); + } else { + $blockEl.after(pasteContent); + this.editor.selection.setRangeAtEndOf(pasteContent); + } + } else { + $blockEl.after(pasteContent); + this.editor.selection.setRangeAtEndOf(pasteContent); + } + } else { + if ($blockEl.is('li')) { + $blockEl = $blockEl.parent(); + } + if (this.editor.selection.rangeAtStartOf($blockEl)) { + insertPosition = 'before'; + } else if (this.editor.selection.rangeAtEndOf($blockEl)) { + insertPosition = 'after'; + } else { + this.editor.selection.breakBlockEl($blockEl); + insertPosition = 'before'; + } + $blockEl[insertPosition](pasteContent); + this.editor.selection.setRangeAtEndOf(pasteContent.last()); + } + return this.editor.inputManager.throttledValueChanged(); + }; + + return Clipboard; + +})(SimpleModule); + Simditor = (function(superClass) { extend(Simditor, superClass); function Simditor() { return Simditor.__super__.constructor.apply(this, arguments); @@ -2319,10 +2407,12 @@ Simditor.connect(Toolbar); Simditor.connect(Indentation); + Simditor.connect(Clipboard); + Simditor.count = 0; Simditor.prototype.opts = { textarea: null, placeholder: '', @@ -2344,10 +2434,18 @@ if (editor != null) { editor.destroy(); } this.id = ++Simditor.count; this._render(); + if (simpleHotkeys) { + this.hotkeys = simpleHotkeys({ + el: this.body + }); + } else { + throw new Error('simditor: simple-hotkeys is required.'); + return; + } if (this.opts.upload && simpleUploader) { uploadOpts = typeof this.opts.upload === 'object' ? this.opts.upload : {}; this.uploader = simpleUploader(uploadOpts); } form = this.textarea.closest('form'); @@ -2468,17 +2566,24 @@ this.textarea.val(val); return val; }; Simditor.prototype.focus = function() { + var $blockEl, range; if (!(this.body.is(':visible') && this.body.is('[contenteditable]'))) { this.el.find('textarea:visible').focus(); return; } if (this.inputManager.lastCaretPosition) { return this.undoManager.caretPosition(this.inputManager.lastCaretPosition); } else { + $blockEl = this.body.children().last(); + if (!$blockEl.is('p')) { + $blockEl = $('<p/>').append(this.util.phBr).appendTo(this.body); + } + range = document.createRange(); + this.selection.setRangeAtEndOf($blockEl, range); return this.body.focus(); } }; Simditor.prototype.blur = function() { @@ -2696,19 +2801,19 @@ }); this.editor.on('blur', (function(_this) { return function() { var editorActive; editorActive = _this.editor.body.is(':visible') && _this.editor.body.is('[contenteditable]'); - if (!editorActive) { + if (!(editorActive && !_this.editor.clipboard.pasting)) { return; } _this.setActive(false); return _this.setDisabled(false); }; })(this)); if (this.shortcut != null) { - this.editor.inputManager.addShortcut(this.shortcut, (function(_this) { + this.editor.hotkeys.add(this.shortcut, (function(_this) { return function(e) { _this.el.mousedown(); return false; }; })(this)); @@ -3508,11 +3613,10 @@ }); }; CodeButton.prototype._status = function() { CodeButton.__super__._status.call(this); - console.log('test'); if (this.active) { return this.popover.show(this.node); } else { return this.popover.hide(); } @@ -3751,11 +3855,11 @@ $link = $('<a/>', { href: 'http://www.example.com', target: '_blank', text: linkText || this._t('linkText') }); - if (this.editor.selection.blockNodes().length === 1) { + if (this.editor.selection.blockNodes().length > 0) { range.insertNode($link[0]); } else { $newBlock = $('<p/>').append($link); range.insertNode($newBlock[0]); } @@ -4080,25 +4184,18 @@ return $mask.find('.progress').height((100 - percent) + "%"); }, 500), this); this.editor.uploader.on('uploadprogress', uploadProgress); this.editor.uploader.on('uploadsuccess', (function(_this) { return function(e, file, result) { - var $img, $mask, msg; + var $img, img_path, msg; if (!file.inline) { return; } $img = file.img; if (!($img.hasClass('uploading') && $img.parent().length > 0)) { return; } - $img.removeData('file'); - $img.removeClass('uploading').removeClass('loading'); - $mask = $img.data('mask'); - if ($mask) { - $mask.remove(); - } - $img.removeData('mask'); if (typeof result !== 'object') { try { result = $.parseJSON(result); } catch (_error) { e = _error; @@ -4108,14 +4205,24 @@ } } if (result.success === false) { msg = result.msg || _this._t('uploadFailed'); alert(msg); - $img.attr('src', _this.defaultImage); + img_path = _this.defaultImage; } else { - $img.attr('src', result.file_path); + img_path = result.file_path; } + _this.loadImage($img, img_path, function() { + var $mask; + $img.removeData('file'); + $img.removeClass('uploading').removeClass('loading'); + $mask = $img.data('mask'); + if ($mask) { + $mask.remove(); + } + return $img.removeData('mask'); + }); if (_this.popover.active) { _this.popover.srcEl.prop('disabled', false); _this.popover.srcEl.val(result.file_path); } _this.editor.trigger('valuechanged'); @@ -4124,11 +4231,11 @@ } }; })(this)); return this.editor.uploader.on('uploaderror', (function(_this) { return function(e, file, xhr) { - var $img, $mask, msg, result; + var $img, msg, result; if (!file.inline) { return; } if (xhr.statusText === 'abort') { return; @@ -4145,18 +4252,20 @@ } $img = file.img; if (!($img.hasClass('uploading') && $img.parent().length > 0)) { return; } - $img.removeData('file'); - $img.removeClass('uploading').removeClass('loading'); - $mask = $img.data('mask'); - if ($mask) { - $mask.remove(); - } - $img.removeData('mask'); - $img.attr('src', _this.defaultImage); + _this.loadImage($img, _this.defaultImage, function() { + var $mask; + $img.removeData('file'); + $img.removeClass('uploading').removeClass('loading'); + $mask = $img.data('mask'); + if ($mask) { + $mask.remove(); + } + return $img.removeData('mask'); + }); if (_this.popover.active) { _this.popover.srcEl.prop('disabled', false); _this.popover.srcEl.val(_this.defaultImage); } _this.editor.trigger('valuechanged'); @@ -4214,15 +4323,19 @@ positionMask(); } else { $mask.remove(); $img.removeData('mask'); } - return callback(img); + if ($.isFunction(callback)) { + return callback(img); + } }; })(this); img.onerror = function() { - callback(false); + if ($.isFunction(callback)) { + callback(false); + } $mask.remove(); return $img.removeData('mask').removeClass('loading'); }; return img.src = src; }; @@ -4672,29 +4785,29 @@ this.editor.on('blur.table', (function(_this) { return function(e) { return _this.editor.body.find('.simditor-table td, .simditor-table th').removeClass('active'); }; })(this)); - this.editor.inputManager.addKeystrokeHandler('38', 'td', (function(_this) { + this.editor.keystroke.add('up', 'td', (function(_this) { return function(e, $node) { _this._tdNav($node, 'up'); return true; }; })(this)); - this.editor.inputManager.addKeystrokeHandler('38', 'th', (function(_this) { + this.editor.keystroke.add('up', 'th', (function(_this) { return function(e, $node) { _this._tdNav($node, 'up'); return true; }; })(this)); - this.editor.inputManager.addKeystrokeHandler('40', 'td', (function(_this) { + this.editor.keystroke.add('down', 'td', (function(_this) { return function(e, $node) { _this._tdNav($node, 'down'); return true; }; })(this)); - return this.editor.inputManager.addKeystrokeHandler('40', 'th', (function(_this) { + return this.editor.keystroke.add('down', 'th', (function(_this) { return function(e, $node) { _this._tdNav($node, 'down'); return true; }; })(this)); @@ -4818,28 +4931,28 @@ return false; }); }; TableButton.prototype._initShortcuts = function() { - this.editor.inputManager.addShortcut('ctrl+alt+up', (function(_this) { + this.editor.hotkeys.add('ctrl+alt+up', (function(_this) { return function(e) { _this.editMenu.find('.menu-item[data-param=insertRowAbove]').click(); return false; }; })(this)); - this.editor.inputManager.addShortcut('ctrl+alt+down', (function(_this) { + this.editor.hotkeys.add('ctrl+alt+down', (function(_this) { return function(e) { _this.editMenu.find('.menu-item[data-param=insertRowBelow]').click(); return false; }; })(this)); - this.editor.inputManager.addShortcut('ctrl+alt+left', (function(_this) { + this.editor.hotkeys.add('ctrl+alt+left', (function(_this) { return function(e) { _this.editMenu.find('.menu-item[data-param=insertColLeft]').click(); return false; }; })(this)); - return this.editor.inputManager.addShortcut('ctrl+alt+right', (function(_this) { + return this.editor.hotkeys.add('ctrl+alt+right', (function(_this) { return function(e) { _this.editMenu.find('.menu-item[data-param=insertColRight]').click(); return false; }; })(this));