From ce6f8632cc272ca616ebc6bdedfa6347f4592b25 Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Fri, 27 Apr 2018 12:28:57 +0200 Subject: [PATCH] [FIX] web_editor: update summernote copy-paste feature The copy-paste feature of summernote was messy and led most of the time to breaking the template you were trying to edit. While it is difficult to solve as it mostly comes from the lib itself and that the lib is in a complicated state as Odoo had to overridde part of its internal code for its own purposes, this commit hopes to solve the problem by updating only the copy-paste feature to the latest summernote version. Of course, as summernote changed a lot meanwhile, it was not possible to just take the up-to-date summernote file. This commit refactores the up-to-date summernote clipboard code so that it fits the old summernote version Odoo is using. Note: the main problems occurred while using Windows/Mac and not Linux. opw-1837818 --- .../lib/summernote/src/js/module/Clipboard.js | 281 ++---------------- 1 file changed, 33 insertions(+), 248 deletions(-) diff --git a/addons/web_editor/static/lib/summernote/src/js/module/Clipboard.js b/addons/web_editor/static/lib/summernote/src/js/module/Clipboard.js index 955b49437a8..4cd9088da75 100644 --- a/addons/web_editor/static/lib/summernote/src/js/module/Clipboard.js +++ b/addons/web_editor/static/lib/summernote/src/js/module/Clipboard.js @@ -1,256 +1,41 @@ define([ - 'summernote/core/list', - 'summernote/core/dom', - 'summernote/core/key', - 'summernote/core/agent', - 'summernote/core/range' + 'summernote/core/list', + 'summernote/core/dom', + 'summernote/core/key', + 'summernote/core/agent', + 'summernote/core/range' ], function (list, dom, key, agent, range) { - var Clipboard = function (handler) { - var $paste; + // ODOO override: use 0.8.10 version of this, adapted for the old summernote + // version odoo is using + var Clipboard = function (handler) { + /** + * paste by clipboard event + * + * @param {Event} event + */ + var pasteByEvent = function (event) { + if (["INPUT", "TEXTAREA"].indexOf(event.target.tagName) !== -1) { + // ODOO override: from old summernote version + return; + } - this.attach = function (layoutInfo) { - // [workaround] getting image from clipboard - // - IE11 and Firefox: CTRL+v hook - // - Webkit: event.clipboardData - if (agent.isMSIE && agent.browserVersion > 10) { - $paste = $('
').attr('contenteditable', true).css({ - position : 'absolute', - left : -100000, - opacity : 0 - }); + var clipboardData = event.originalEvent.clipboardData; + var layoutInfo = dom.makeLayoutInfo(event.currentTarget || event.target); + var $editable = layoutInfo.editable(); - layoutInfo.editable().on('keydown', function (e) { - if (e.ctrlKey && e.keyCode === key.code.V) { - handler.invoke('saveRange', layoutInfo.editable()); - $paste.focus(); + if (clipboardData && clipboardData.items && clipboardData.items.length) { + var item = list.head(clipboardData.items); + if (item.kind === 'file' && item.type.indexOf('image/') !== -1) { + handler.insertImages(layoutInfo, [item.getAsFile()]); + } + handler.invoke('editor.afterCommand', $editable); + } + }; - setTimeout(function () { - pasteByHook(layoutInfo); - }, 0); - } - }); - - layoutInfo.editable().before($paste); - } else { - layoutInfo.editable().on('paste', pasteByEvent); - } + this.attach = function (layoutInfo) { + layoutInfo.editable().on('paste', pasteByEvent); + }; }; - var pasteByHook = function (layoutInfo) { - var $editable = layoutInfo.editable(); - var node = $paste[0].firstChild; - - if (dom.isImg(node)) { - var dataURI = node.src; - var decodedData = atob(dataURI.split(',')[1]); - var array = new Uint8Array(decodedData.length); - for (var i = 0; i < decodedData.length; i++) { - array[i] = decodedData.charCodeAt(i); - } - - var blob = new Blob([array], { type : 'image/png' }); - blob.name = 'clipboard.png'; - - handler.invoke('restoreRange', $editable); - handler.invoke('focus', $editable); - handler.insertImages(layoutInfo, [blob]); - } else { - var pasteContent = $('
').html($paste.html()).html(); - handler.invoke('restoreRange', $editable); - handler.invoke('focus', $editable); - - if (pasteContent) { - handler.invoke('pasteHTML', $editable, pasteContent); - } - } - - $paste.empty(); - }; - - /** - * paste by clipboard event - * - * @param {Event} event - */ - var pasteByEvent = function (event) { - var clipboardData = event.originalEvent.clipboardData; - var layoutInfo = dom.makeLayoutInfo(event.currentTarget || event.target); - var $editable = layoutInfo.editable(); - - if (["INPUT", "TEXTAREA"].indexOf(event.target.tagName) !== -1) { - return; - } - - if (clipboardData && clipboardData.items && clipboardData.items.length) { - var item = list.head(clipboardData.items); - if (item.kind === 'file' && item.type.indexOf('image/') !== -1) { - handler.insertImages(layoutInfo, [item.getAsFile()]); - } - handler.invoke('editor.afterCommand', $editable); - } - - event.preventDefault(); - - var html = clipboardData.getData("text/html"); - var $node = $('
').html(html); - // if copying source did not provide html, default to plain text - if(!html) { - $node.text(clipboardData.getData("text/plain")).html(function(_, html){ - return html.replace(/\r?\n/g,'
'); - }); - } - pasteContent($node, layoutInfo, $editable); - }; - - /* - remove undesirable tag - filter classes and style attributes - remove undesirable attributes - */ - var filter_tag = function ($nodes, $editable) { - return $nodes.each(function() { - var $node = $(this); - - if ($node.attr('style')) { - var style = _.filter(_.compact($node.attr('style').split(/\s*;\s*/)), function (style) { - style = style.split(/\s*:\s*/); - return /width|height|color|background-color|font-weight|text-align|font-style|text-decoration/i.test(style[0]) && - !(style[1] === 'initial' || style[1] === 'inherit' || $node.css(style[0]) === $editable.css(style[0]) || - (style[0] === 'background-color' && style[1] === 'rgb(255, 255, 255)') || - (style[0] === 'color' && style[1] === 'rgb(0, 0, 0)')); - }).join(';'); - if (style.length) { - $node.attr('style', style); - } else { - $node.removeAttr('style'); - } - } - - if ($node.attr('class')) { - var classes = _.filter($node.attr('class').split(/\s+/), function (style) { - return /(^|\s)(fa|pull|text|bg)(\s|-|$)/.test(style); - }).join(' '); - if (classes.length) { - $node.attr('class', classes); - } else { - $node.removeAttr('class'); - } - } - }); - }; - - var pasteContent = function ($node, layoutInfo, $editable) { - $node.find('meta, script, style').remove(); - filter_tag($node.find('*'), $editable).removeAttr('title', 'alt', 'id', 'contenteditable'); - - /* - remove unless span and unwant font - */ - $node.find('span, font').filter(':not([class]):not([style])').each(function () { - $(this).replaceWith($(this).contents()); - }); - $node.find('span + span').each(function () { - - if (dom.isText(this.previousSibling)) { - if (dom.isVisibleText(this.previousSibling)) { - return; - } else { // keep space between 2 tags, but can merge the both tags - $(this).prev().append(this.previousSibling); - } - } - if ($(this).attr('class') === $(this).prev().attr('class') && $(this).attr('style') === $(this).prev().attr('style')) { - $(this).prev().append($(this).contents()); - $(this).remove(); - } - }); - - // remove empty table row and td - var $tdr; - while(($tdr = $node.find('tr:empty, td:empty, th:empty, tbody:empty, t-head:empty, table:empty')) && $tdr.length) { - $tdr.remove(); - } - - /* - reset architecture HTML node and add

tag - */ - var $arch = $('

'); - var $last = $arch; - $node.contents().each(function () { - if (dom.isBR(this)) { - $(this).remove(); - $last = $('

'); - $arch.append($last); - } else if (/h[0-9]+|li|table|p/i.test(this.tagName)) { - $last = $('

'); - $arch.append(this).append($last); - } else if ($arch.is(':empty') && dom.isText(this)) { - $last = $('

').append(this); - $arch.append($last); - } else if (this.nodeType !== Node.COMMENT_NODE) { - $last.append(this); - } - }); - $arch.find(':not([class]):not([style]):empty, p:empty').remove(); - - /* - history - */ - $editable.data('NoteHistory').recordUndo($editable, "paste"); - - /* - remove selected content - */ - var r = range.create(); - if (!r.isCollapsed()) { - r = r.deleteContents(); - r.select(); - } - - // If only pasting a

element in an unique

element, only paste - // the

element text - var $p = $arch.children('p'); - var onlyAP = ($p.length === 1 && $arch.children().length === 1); - if (onlyAP) { - var $p1 = $(r.sc).closest('p'); - var $p2 = $(r.ec).closest('p'); - if ($p1.length && $p2.length && $p1[0] === $p2[0]) { - $arch.html($p.text()); - } - } - - /* - insert content - */ - var $nodes = $(); - $editable.on('DOMNodeInserted', function (event) { - $nodes = $nodes.add(event.originalEvent.target); - }); - window.document.execCommand('insertHTML', false, $arch.html()); - $editable.off('DOMNodeInserted'); - - /* - clean insterted content - */ - var $span = $nodes.filter('span'); - $span = $span.first().add($span.last()); - $span = $span.add($span.prev('span')); - $span = $span.add($span.next('span')); - filter_tag($span, $editable); - $span.not('[span], [style]').each(function () { - _.each(this.childNodes, function (node) { - $(node.parentNode).after(node); - }); - $(this).remove(); - }); - r = range.create(); - if (!dom.isText(r.ec)) { - r = range.create(r.sc.childNodes[r.so], dom.nodeLength(r.sc.childNodes[r.so])); - } - r.clean().select(); - - $editable.trigger('content_changed'); - }; - }; - - return Clipboard; + return Clipboard; });