diff options
| author | Nicolas Lempereur <nle@odoo.com> | 2015-09-25 10:53:08 +0200 |
|---|---|---|
| committer | Nicolas Lempereur <nle@odoo.com> | 2015-09-25 10:53:08 +0200 |
| commit | eed63c793a55140b036bf807914e93c2a8bb37ad (patch) | |
| tree | 6a205e37eeac6c60f84f58191392b95457e1afac /addons/web_editor/static/lib | |
| parent | 15d2778f0f1f2fbdbe829530c82929b1efb51f5f (diff) | |
Revert "[IMP] web_editor: update summernote up to 2015-08-31"
Revert till several issues are solved.
Diffstat (limited to 'addons/web_editor/static/lib')
34 files changed, 1875 insertions, 3705 deletions
diff --git a/addons/web_editor/static/lib/summernote/src/js/EventHandler.js b/addons/web_editor/static/lib/summernote/src/js/EventHandler.js index 09a7b00082a..068ceff735f 100644 --- a/addons/web_editor/static/lib/summernote/src/js/EventHandler.js +++ b/addons/web_editor/static/lib/summernote/src/js/EventHandler.js @@ -1,131 +1,92 @@ define([ - 'summernote/core/agent', - 'summernote/core/func', - 'summernote/core/dom', - 'summernote/core/async', - 'summernote/core/key', - 'summernote/core/list', - 'summernote/editing/History', - 'summernote/module/Editor', - 'summernote/module/Toolbar', - 'summernote/module/Statusbar', - 'summernote/module/Popover', - 'summernote/module/Handle', - 'summernote/module/Fullscreen', - 'summernote/module/Codeview', - 'summernote/module/DragAndDrop', - 'summernote/module/Clipboard', - 'summernote/module/LinkDialog', - 'summernote/module/ImageDialog', - 'summernote/module/HelpDialog' -], function (agent, func, dom, async, key, list, History, - Editor, Toolbar, Statusbar, Popover, Handle, Fullscreen, Codeview, - DragAndDrop, Clipboard, LinkDialog, ImageDialog, HelpDialog) { + 'summernote/core/agent', 'summernote/core/dom', 'summernote/core/async', 'summernote/core/key', 'summernote/core/list', 'summernote/core/range', + 'summernote/editing/Style', 'summernote/editing/Editor', 'summernote/editing/History', + 'summernote/module/Toolbar', 'summernote/module/Popover', 'summernote/module/Handle', 'summernote/module/Dialog' +], function (agent, dom, async, key, list, range, + Style, Editor, History, + Toolbar, Popover, Handle, Dialog) { + + var CodeMirror; + if (agent.hasCodeMirror) { + if (agent.isSupportAmd) { + require(['CodeMirror'], function (cm) { + CodeMirror = cm; + }); + } else { + CodeMirror = window.CodeMirror; + } + } /** - * @class EventHandler - * * EventHandler - * - TODO: new instance per a editor */ var EventHandler = function () { - var self = this; + var $window = $(window); + var $document = $(document); + var $scrollbar = $('html, body'); - /** - * Modules - */ - var modules = this.modules = { - editor: new Editor(this), - toolbar: new Toolbar(this), - statusbar: new Statusbar(this), - popover: new Popover(this), - handle: new Handle(this), - fullscreen: new Fullscreen(this), - codeview: new Codeview(this), - dragAndDrop: new DragAndDrop(this), - clipboard: new Clipboard(this), - linkDialog: new LinkDialog(this), - imageDialog: new ImageDialog(this), - helpDialog: new HelpDialog(this) + var editor = new Editor(); + var toolbar = new Toolbar(), popover = new Popover(); + var handle = new Handle(), dialog = new Dialog(); + + this.getEditor = function () { + return editor; }; /** - * invoke module's method + * returns makeLayoutInfo from editor's descendant node. * - * @param {String} moduleAndMethod - ex) 'editor.redo' - * @param {...*} arguments - arguments of method - * @return {*} + * @param {Node} descendant + * @returns {Object} */ - this.invoke = function () { - var moduleAndMethod = list.head(list.from(arguments)); - var args = list.tail(list.from(arguments)); - - var splits = moduleAndMethod.split('.'); - var hasSeparator = splits.length > 1; - var moduleName = hasSeparator && list.head(splits); - var methodName = hasSeparator ? list.last(splits) : list.head(splits); - - var module = this.getModule(moduleName); - var method = module[methodName]; + var makeLayoutInfo = function (descendant) { + var $target = $(descendant).closest('.note-editor, .note-air-editor, .note-air-layout'); - return method && method.apply(module, args); - }; + if (!$target.length) { return null; } - /** - * returns module - * - * @param {String} moduleName - name of module - * @return {Module} - defaults is editor - */ - this.getModule = function (moduleName) { - return this.modules[moduleName] || this.modules.editor; - }; + var $editor; + if ($target.is('.note-editor, .note-air-editor')) { + $editor = $target; + } else { + $editor = $('[data-note-id="' + list.last($target.attr('id').split('-')) + '"]'); + } - /** - * @param {jQuery} $holder - * @param {Object} callbacks - * @param {String} eventNamespace - * @returns {Function} - */ - var bindCustomEvent = this.bindCustomEvent = function ($holder, callbacks, eventNamespace) { - return function () { - var callback = callbacks[func.namespaceToCamel(eventNamespace, 'on')]; - if (callback) { - callback.apply($holder[0], arguments); - } - return $holder.trigger('summernote.' + eventNamespace, arguments); - }; + return dom.buildLayoutInfo($editor); }; /** * insert Images from file array. * - * @private * @param {Object} layoutInfo * @param {File[]} files */ - this.insertImages = function (layoutInfo, files) { + var insertImages = function (layoutInfo, files) { var $editor = layoutInfo.editor(), - $editable = layoutInfo.editable(), - $holder = layoutInfo.holder(); + $editable = layoutInfo.editable(); var callbacks = $editable.data('callbacks'); var options = $editor.data('options'); // If onImageUpload options setted if (callbacks.onImageUpload) { - bindCustomEvent($holder, callbacks, 'image.upload')(files); + callbacks.onImageUpload(files, editor, $editable); // else insert Image as dataURL } else { $.each(files, function (idx, file) { var filename = file.name; if (options.maximumImageFileSize && options.maximumImageFileSize < file.size) { - bindCustomEvent($holder, callbacks, 'image.upload.error')(options.langInfo.image.maximumFileSizeError); + if (callbacks.onImageUploadError) { + callbacks.onImageUploadError(options.langInfo.image.maximumFileSizeError); + } else { + alert(options.langInfo.image.maximumFileSizeError); + } } else { async.readFileAsDataURL(file).then(function (sDataURL) { - modules.editor.insertImage($editable, sDataURL, filename); + editor.insertImage($editable, sDataURL, filename); }).fail(function () { - bindCustomEvent($holder, callbacks, 'image.upload.error')(options.langInfo.image.maximumFileSizeError); + if (callbacks.onImageUploadError) { + callbacks.onImageUploadError(); + } }); } }); @@ -137,90 +98,292 @@ define([ * @param {Object} layoutInfo */ showLinkDialog: function (layoutInfo) { - modules.linkDialog.show(layoutInfo); + var $editor = layoutInfo.editor(), + $dialog = layoutInfo.dialog(), + $editable = layoutInfo.editable(), + linkInfo = editor.getLinkInfo($editable); + + var options = $editor.data('options'); + + editor.saveRange($editable); + dialog.showLinkDialog($editable, $dialog, linkInfo).then(function (linkInfo) { + editor.restoreRange($editable); + editor.createLink($editable, linkInfo, options); + // hide popover after creating link + popover.hide(layoutInfo.popover()); + }).fail(function () { + editor.restoreRange($editable); + }); }, /** * @param {Object} layoutInfo */ showImageDialog: function (layoutInfo) { - modules.imageDialog.show(layoutInfo); + var $dialog = layoutInfo.dialog(), + $editable = layoutInfo.editable(); + + editor.saveRange($editable); + dialog.showImageDialog($editable, $dialog).then(function (data) { + editor.restoreRange($editable); + + if (typeof data === 'string') { + // image url + editor.insertImage($editable, data); + } else { + // array of files + insertImages(layoutInfo, data); + } + }).fail(function () { + editor.restoreRange($editable); + }); }, /** * @param {Object} layoutInfo */ showHelpDialog: function (layoutInfo) { - modules.helpDialog.show(layoutInfo); + var $dialog = layoutInfo.dialog(), + $editable = layoutInfo.editable(); + + editor.saveRange($editable, true); + dialog.showHelpDialog($editable, $dialog).then(function () { + editor.restoreRange($editable); + }); }, - /** - * @param {Object} layoutInfo - */ fullscreen: function (layoutInfo) { - modules.fullscreen.toggle(layoutInfo); + var $editor = layoutInfo.editor(), + $toolbar = layoutInfo.toolbar(), + $editable = layoutInfo.editable(), + $codable = layoutInfo.codable(); + + var resize = function (size) { + $editable.css('height', size.h); + $codable.css('height', size.h); + if ($codable.data('cmeditor')) { + $codable.data('cmeditor').setsize(null, size.h); + } + }; + + $editor.toggleClass('fullscreen'); + var isFullscreen = $editor.hasClass('fullscreen'); + if (isFullscreen) { + $editable.data('orgheight', $editable.css('height')); + + $window.on('resize', function () { + resize({ + h: $window.height() - $toolbar.outerHeight() + }); + }).trigger('resize'); + + $scrollbar.css('overflow', 'hidden'); + } else { + $window.off('resize'); + resize({ + h: $editable.data('orgheight') + }); + $scrollbar.css('overflow', 'visible'); + } + + toolbar.updateFullscreen($toolbar, isFullscreen); }, - /** - * @param {Object} layoutInfo - */ codeview: function (layoutInfo) { - modules.codeview.toggle(layoutInfo); + var $editor = layoutInfo.editor(), + $toolbar = layoutInfo.toolbar(), + $editable = layoutInfo.editable(), + $codable = layoutInfo.codable(), + $popover = layoutInfo.popover(), + $handle = layoutInfo.handle(); + + var options = $editor.data('options'); + + var cmEditor, server; + + $editor.toggleClass('codeview'); + + var isCodeview = $editor.hasClass('codeview'); + if (isCodeview) { + $codable.val(dom.html($editable, true)); + $codable.height($editable.height()); + toolbar.deactivate($toolbar); + popover.hide($popover); + handle.hide($handle); + $codable.focus(); + + // activate CodeMirror as codable + if (agent.hasCodeMirror) { + cmEditor = CodeMirror.fromTextArea($codable[0], options.codemirror); + + // CodeMirror TernServer + if (options.codemirror.tern) { + server = new CodeMirror.TernServer(options.codemirror.tern); + cmEditor.ternServer = server; + cmEditor.on('cursorActivity', function (cm) { + server.updateArgHints(cm); + }); + } + + // CodeMirror hasn't Padding. + cmEditor.setSize(null, $editable.outerHeight()); + $codable.data('cmEditor', cmEditor); + } + } else { + // deactivate CodeMirror as codable + if (agent.hasCodeMirror) { + cmEditor = $codable.data('cmEditor'); + $codable.val(cmEditor.getValue()); + cmEditor.toTextArea(); + } + + $editable.html(dom.value($codable) || dom.emptyPara); + $editable.height(options.height ? $codable.height() : 'auto'); + + toolbar.activate($toolbar); + $editable.focus(); + } + + toolbar.updateCodeview(layoutInfo.toolbar(), isCodeview); } }; + this.commands = commands; // odoo change for overwrite + this.editor = editor; // odoo change for overwrite + this.toolbar = toolbar; // odoo change for overwrite + this.popover = popover; // odoo change for overwrite + this.handle = handle; // odoo change for overwrite + this.dialog = dialog; // odoo change for overwrite + var hMousedown = function (event) { //preventDefault Selection for FF, IE8+ if (dom.isImg(event.target)) { event.preventDefault(); + range.createFromNode(event.target).select(); } }; - var hKeyupAndMouseup = function (event) { - var layoutInfo = dom.makeLayoutInfo(event.currentTarget || event.target); - modules.editor.removeBogus(layoutInfo.editable()); - hToolbarAndPopoverUpdate(event); + var hToolbarAndPopoverUpdate = function (event) { + // delay for range after mouseup + setTimeout(function () { + var layoutInfo = makeLayoutInfo(event.target); + var $editable = layoutInfo.editable(); + editor.saveRange($editable); + var styleInfo = editor.currentStyle(); + if (!styleInfo) { return; } + + var isAirMode = layoutInfo.editor().data('options').airMode; + if (!isAirMode) { + toolbar.update(layoutInfo.toolbar(), styleInfo); + } + + popover.update(layoutInfo.popover(), styleInfo, isAirMode); + handle.update(layoutInfo.handle(), styleInfo, isAirMode); + }, 0); + }; + + var hScroll = function (event) { + var layoutInfo = makeLayoutInfo(event.currentTarget || event.target); + //hide popover and handle when scrolled + popover.hide(layoutInfo.popover()); + handle.hide(layoutInfo.handle()); }; /** - * update sytle info - * @param {Object} styleInfo - * @param {Object} layoutInfo + * paste clipboard image + * + * @param {Event} event */ - this.updateStyleInfo = function (styleInfo, layoutInfo) { - if (!styleInfo) { + var hPasteClipboardImage = function (event) { + var clipboardData = event.originalEvent.clipboardData; + var layoutInfo = makeLayoutInfo(event.currentTarget || event.target); + var $editable = layoutInfo.editable(); + + if (!clipboardData || !clipboardData.items || !clipboardData.items.length) { + var callbacks = $editable.data('callbacks'); + // only can run if it has onImageUpload method + if (!callbacks.onImageUpload) { + return; + } + + // save cursor + editor.saveNode($editable); + editor.saveRange($editable); + + $editable.html(''); + + setTimeout(function () { + var $img = $editable.find('img'); + var datauri = $img[0].src; + + var data = atob(datauri.split(',')[1]); + var array = new Uint8Array(data.length); + for (var i = 0; i < data.length; i++) { + array[i] = data.charCodeAt(i); + } + + var blob = new Blob([array], { type : 'image/png'}); + blob.name = 'clipboard.png'; + + editor.restoreNode($editable); + editor.restoreRange($editable); + insertImages(layoutInfo, [blob]); + + editor.afterCommand($editable); + }, 0); + return; } - var isAirMode = layoutInfo.editor().data('options').airMode; - if (!isAirMode) { - modules.toolbar.update(layoutInfo.toolbar(), styleInfo); + + var item = list.head(clipboardData.items); + var isClipboardImage = item.kind === 'file' && item.type.indexOf('image/') !== -1; + + if (isClipboardImage) { + insertImages(layoutInfo, [item.getAsFile()]); } - modules.popover.update(layoutInfo.popover(), styleInfo, isAirMode); - modules.handle.update(layoutInfo.handle(), styleInfo, isAirMode); + editor.afterCommand($editable); }; - var hToolbarAndPopoverUpdate = function (event) { - var target = event.target; - // delay for range after mouseup - setTimeout(function () { - var layoutInfo = dom.makeLayoutInfo(target); - /* ODOO: (start_modification */ - var $editable = layoutInfo.editable(); - if (!event.isDefaultPrevented()) { - modules.editor.saveRange($editable); - } - /* ODOO: end_modification) */ - var styleInfo = modules.editor.currentStyle(target); - self.updateStyleInfo(styleInfo, layoutInfo); - }, 0); - }; + /** + * `mousedown` event handler on $handle + * - controlSizing: resize image + * + * @param {MouseEvent} event + */ + var hHandleMousedown = function (event) { + if (dom.isControlSizing(event.target)) { + event.preventDefault(); + event.stopPropagation(); + + var layoutInfo = makeLayoutInfo(event.target), + $handle = layoutInfo.handle(), $popover = layoutInfo.popover(), + $editable = layoutInfo.editable(), + $editor = layoutInfo.editor(); + + var target = $handle.find('.note-control-selection').data('target'), + $target = $(target), posStart = $target.offset(), + scrollTop = $document.scrollTop(); + + var isAirMode = $editor.data('options').airMode; + + $document.on('mousemove', function (event) { + editor.resizeTo({ + x: event.clientX - posStart.left, + y: event.clientY - (posStart.top - scrollTop) + }, $target, !event.shiftKey); + + handle.update($handle, {image: target}, isAirMode); + popover.update($popover, {image: target}, isAirMode); + }).one('mouseup', function () { + $document.off('mousemove'); + editor.afterCommand($editable); + }); - var hScroll = function (event) { - var layoutInfo = dom.makeLayoutInfo(event.currentTarget || event.target); - //hide popover and handle when scrolled - modules.popover.hide(layoutInfo.popover()); - modules.handle.hide(layoutInfo.handle()); + if (!$target.data('ratio')) { // original ratio. + $target.data('ratio', $target.height() / $target.width()); + } + } }; var hToolbarAndPopoverMousedown = function (event) { @@ -234,49 +397,76 @@ define([ var hToolbarAndPopoverClick = function (event) { var $btn = $(event.target).closest('[data-event]'); - if (!$btn.length) { - return; - } + if ($btn.length && !$btn.is(':disabled')) { + var eventName = $btn.attr('data-event'), + value = $btn.attr('data-value'), + hide = $btn.attr('data-hide'); - var eventName = $btn.attr('data-event'), - value = $btn.attr('data-value'), - hide = $btn.attr('data-hide'); + var layoutInfo = makeLayoutInfo(event.target); - var layoutInfo = dom.makeLayoutInfo(event.target); + var $editable = layoutInfo.editable(); + editor.restoreRange($editable); - // before command: detect control selection element($target) - var $target; - if ($.inArray(eventName, ['resize', 'floatMe', 'removeMedia', 'imageShape']) !== -1) { - var $selection = layoutInfo.handle().find('.note-control-selection'); - $target = $($selection.data('target')); - } + // before command: detect control selection element($target) + var $target; + if ($.inArray(eventName, ['resize', 'floatMe', 'removeMedia', 'imageShape']) !== -1) { + var $selection = layoutInfo.handle().find('.note-control-selection'); + $target = $($selection.data('target')); + } - // If requested, hide the popover when the button is clicked. - // Useful for things like showHelpDialog. - if (hide) { - $btn.parents('.popover').hide(); - } + // If requested, hide the popover when the button is clicked. + // Useful for things like showHelpDialog. + if (hide) { + $btn.parents('.popover').hide(); + } - if ($.isFunction($.summernote.pluginEvents[eventName])) { - $.summernote.pluginEvents[eventName](event, modules.editor, layoutInfo, value); - } else if (modules.editor[eventName]) { // on command - var $editable = layoutInfo.editable(); - $editable.focus(); - modules.editor[eventName]($editable, value, $target); - event.preventDefault(); - } else if (commands[eventName]) { - commands[eventName].call(this, layoutInfo); - event.preventDefault(); - } + if ($.isFunction($.summernote.pluginEvents[eventName])) { + $.summernote.pluginEvents[eventName](event, editor, layoutInfo, value); + } else if (editor[eventName]) { // on command + $editable.trigger('focus'); + editor[eventName]($editable, value, $target); + event.preventDefault(); + } else if (commands[eventName]) { + commands[eventName].call(this, layoutInfo); + event.preventDefault(); + } - // after command - if ($.inArray(eventName, ['backColor', 'foreColor']) !== -1) { - var options = layoutInfo.editor().data('options', options); - var module = options.airMode ? modules.popover : modules.toolbar; - module.updateRecentColor(list.head($btn), eventName, value); + // after command + if ($.inArray(eventName, ['backColor', 'foreColor']) !== -1) { + var options = layoutInfo.editor().data('options', options); + var module = options.airMode ? popover : toolbar; + module.updateRecentColor(list.head($btn), eventName, value); + } + + hToolbarAndPopoverUpdate(event); } + }; + + var EDITABLE_PADDING = 24; + /** + * `mousedown` event handler on statusbar + * + * @param {MouseEvent} event + */ + var hStatusbarMousedown = function (event) { + event.preventDefault(); + event.stopPropagation(); + + var layoutInfo = makeLayoutInfo(event.target); + var $editable = layoutInfo.editable(); + var nEditableTop = $editable.offset().top - $document.scrollTop(); + var options = layoutInfo.editor().data('options'); - hToolbarAndPopoverUpdate(event); + $document.on('mousemove', function (event) { + var nHeight = event.clientY - (nEditableTop + EDITABLE_PADDING); + + nHeight = (options.minHeight > 0) ? Math.max(nHeight, options.minHeight) : nHeight; + nHeight = (options.maxHeight > 0) ? Math.min(nHeight, options.maxHeight) : nHeight; + + $editable.height(nHeight); + }).one('mouseup', function () { + $document.off('mousemove'); + }); }; var PX_PER_EM = 18; @@ -320,7 +510,81 @@ define([ $dimensionDisplay.html(dim.c + ' x ' + dim.r); }; - + + /** + * Drag and Drop Events + * + * @param {Object} layoutInfo - layout Informations + * @param {Object} options + */ + var handleDragAndDropEvent = function (layoutInfo, options) { + if (options.disableDragAndDrop) { + // prevent default drop event + $document.on('drop', function (e) { + e.preventDefault(); + }); + } else { + attachDragAndDropEvent(layoutInfo, options); + } + }; + + /** + * attach Drag and Drop Events + * + * @param {Object} layoutInfo - layout Informations + * @param {Object} options + */ + var attachDragAndDropEvent = function (layoutInfo, options) { + var collection = $(), + $dropzone = layoutInfo.dropzone, + $dropzoneMessage = layoutInfo.dropzone.find('.note-dropzone-message'); + + // show dropzone on dragenter when dragging a object to document. + $document.on('dragenter', function (e) { + var isCodeview = layoutInfo.editor.hasClass('codeview'); + if (!isCodeview && !collection.length) { + layoutInfo.editor.addClass('dragover'); + $dropzone.width(layoutInfo.editor.width()); + $dropzone.height(layoutInfo.editor.height()); + $dropzoneMessage.text(options.langInfo.image.dragImageHere); + } + collection = collection.add(e.target); + }).on('dragleave', function (e) { + collection = collection.not(e.target); + if (!collection.length) { + layoutInfo.editor.removeClass('dragover'); + } + }).on('drop', function () { + collection = $(); + layoutInfo.editor.removeClass('dragover'); + }); + + // change dropzone's message on hover. + $dropzone.on('dragenter', function () { + $dropzone.addClass('hover'); + $dropzoneMessage.text(options.langInfo.image.dropImage); + }).on('dragleave', function () { + $dropzone.removeClass('hover'); + $dropzoneMessage.text(options.langInfo.image.dragImageHere); + }); + + // attach dropImage + $dropzone.on('drop', function (event) { + event.preventDefault(); + + var dataTransfer = event.originalEvent.dataTransfer; + var text = dataTransfer.getData('text/plain'); + var layoutInfo = makeLayoutInfo(event.currentTarget || event.target); + layoutInfo.editable().focus(); + if (dataTransfer && dataTransfer.files && dataTransfer.files.length) { + insertImages(layoutInfo, dataTransfer.files); + } else if (text) { + editor.insertText(layoutInfo.editable(), text); + } + }).on('dragover', false); // prevent default dragover event + }; + + /** * bind KeyMap on keydown * @@ -328,28 +592,25 @@ define([ * @param {Object} keyMap */ this.bindKeyMap = function (layoutInfo, keyMap) { - var $editor = layoutInfo.editor(); - var $editable = layoutInfo.editable(); + var $editor = layoutInfo.editor; + var $editable = layoutInfo.editable; + + layoutInfo = makeLayoutInfo($editable); $editable.on('keydown', function (event) { - var keys = []; + var aKey = []; // modifier - if (event.metaKey) { keys.push('CMD'); } - if (event.ctrlKey && !event.altKey) { keys.push('CTRL'); } - if (event.shiftKey) { keys.push('SHIFT'); } + if (event.metaKey) { aKey.push('CMD'); } + if (event.ctrlKey && !event.altKey) { aKey.push('CTRL'); } + if (event.shiftKey) { aKey.push('SHIFT'); } // keycode var keyName = key.nameFromCode[event.keyCode]; - if (keyName) { - keys.push(keyName); - } + if (keyName) { aKey.push(keyName); } - var pluginEvent; - var keyString = keys.join('+'); - var eventName = keyMap[keyString]; + var eventName = keyMap[aKey.join('+')]; - // ODOO: (start_modification // odoo change: add visible event to overwrite the browser comportment var keycode = event.keyCode; if (!eventName && @@ -362,33 +623,27 @@ define([ (keycode > 218 && keycode < 223))) { // [\]' (in order)) eventName = 'visible'; } else if (!keycode) { - self.invoke('restoreRange', $editable); + editor.restoreRange($editable); } - // ODOO: end_modification) if (eventName) { - // FIXME Summernote doesn't support event pipeline yet. - // - Plugin -> Base Code - pluginEvent = $.summernote.pluginEvents[keyString]; - if ($.isFunction(pluginEvent)) { - if (pluginEvent(event, modules.editor, layoutInfo)) { - return false; + if ($.summernote.pluginEvents[eventName]) { + var plugin = $.summernote.pluginEvents[eventName]; + if ($.isFunction(plugin)) { + plugin(event, editor, layoutInfo); } - } - - pluginEvent = $.summernote.pluginEvents[eventName]; - - if ($.isFunction(pluginEvent)) { - pluginEvent(event, modules.editor, layoutInfo); - } else if (modules.editor[eventName]) { - modules.editor[eventName]($editable, $editor.data('options')); + } else if (editor[eventName]) { + editor[eventName]($editable, $editor.data('options')); event.preventDefault(); } else if (commands[eventName]) { commands[eventName].call(this, layoutInfo); event.preventDefault(); } + if (keycode) { + editor.saveRange($editable); + } } else if (key.isEdit(event.keyCode)) { - modules.editor.afterCommand($editable); + editor.afterCommand($editable); } }); }; @@ -398,40 +653,41 @@ define([ * * @param {Object} layoutInfo - layout Informations * @param {Object} options - user options include custom event handlers + * @param {Function} options.enter - enter key handler */ this.attach = function (layoutInfo, options) { // handlers for editable if (options.shortcuts) { this.bindKeyMap(layoutInfo, options.keyMap[agent.isMac ? 'mac' : 'pc']); } - layoutInfo.editable().on('mousedown', hMousedown); - layoutInfo.editable().on('keyup mouseup', hKeyupAndMouseup); - layoutInfo.editable().on('scroll', hScroll); - - // handler for clipboard - modules.clipboard.attach(layoutInfo, options); + layoutInfo.editable.on('mousedown', hMousedown); + layoutInfo.editable.on('keyup mouseup', hToolbarAndPopoverUpdate); + layoutInfo.editable.on('scroll', hScroll); + layoutInfo.editable.on('paste', hPasteClipboardImage); // handler for handle and popover - modules.handle.attach(layoutInfo, options); - layoutInfo.popover().on('click', hToolbarAndPopoverClick); - layoutInfo.popover().on('mousedown', hToolbarAndPopoverMousedown); - - // handler for drag and drop - modules.dragAndDrop.attach(layoutInfo, options); + layoutInfo.handle.on('mousedown', hHandleMousedown); + layoutInfo.popover.on('click', hToolbarAndPopoverClick); + layoutInfo.popover.on('mousedown', hToolbarAndPopoverMousedown); // handlers for frame mode (toolbar, statusbar) if (!options.airMode) { + // handler for drag and drop + handleDragAndDropEvent(layoutInfo, options); + // handler for toolbar - layoutInfo.toolbar().on('click', hToolbarAndPopoverClick); - layoutInfo.toolbar().on('mousedown', hToolbarAndPopoverMousedown); + layoutInfo.toolbar.on('click', hToolbarAndPopoverClick); + layoutInfo.toolbar.on('mousedown', hToolbarAndPopoverMousedown); // handler for statusbar - modules.statusbar.attach(layoutInfo, options); + if (!options.disableResizeEditor) { + layoutInfo.statusbar.on('mousedown', hStatusbarMousedown); + } } // handler for table dimension - var $catcherContainer = options.airMode ? layoutInfo.popover() : - layoutInfo.toolbar(); + var $catcherContainer = options.airMode ? layoutInfo.popover : + layoutInfo.toolbar; var $catcher = $catcherContainer.find('.note-dimension-picker-mousecatcher'); $catcher.css({ width: options.insertTableMaxSize.col + 'em', @@ -441,121 +697,72 @@ define([ }); // save options on editor - layoutInfo.editor().data('options', options); + layoutInfo.editor.data('options', options); // ret styleWithCSS for backColor / foreColor clearing with 'inherit'. if (!agent.isMSIE) { - // [workaround] for Firefox - // - protect FF Error: NS_ERROR_FAILURE: Failure + // protect FF Error: NS_ERROR_FAILURE: Failure setTimeout(function () { document.execCommand('styleWithCSS', 0, options.styleWithSpan); }, 0); } // History - var history = new History(layoutInfo.editable()); - layoutInfo.editable().data('NoteHistory', history); + var history = new History(layoutInfo.editable); + layoutInfo.editable.data('NoteHistory', history); + + // basic event callbacks (lowercase) + // enter, focus, blur, keyup, keydown + if (options.onenter) { + layoutInfo.editable.keypress(function (event) { + if (event.keyCode === key.ENTER) { options.onenter(event); } + }); + } + + if (options.onfocus) { layoutInfo.editable.focus(options.onfocus); } + if (options.onblur) { layoutInfo.editable.blur(options.onblur); } + if (options.onkeyup) { layoutInfo.editable.keyup(options.onkeyup); } + if (options.onkeydown) { layoutInfo.editable.keydown(options.onkeydown); } + if (options.onpaste) { layoutInfo.editable.on('paste', options.onpaste); } + + // callbacks for advanced features (camel) + if (options.onToolbarClick) { layoutInfo.toolbar.click(options.onToolbarClick); } + if (options.onChange) { + var hChange = function () { + editor.triggerOnChange(layoutInfo.editable); + }; + + if (agent.isMSIE) { + var sDomEvents = 'DOMCharacterDataModified DOMSubtreeModified DOMNodeInserted'; + layoutInfo.editable.on(sDomEvents, hChange); + } else { + layoutInfo.editable.on('input', hChange); + } + } // All editor status will be saved on editable with jquery's data // for support multiple editor with singleton object. - layoutInfo.editable().data('callbacks', { - onInit: options.onInit, - onFocus: options.onFocus, - onBlur: options.onBlur, - onKeydown: options.onKeydown, - onKeyup: options.onKeyup, - onMousedown: options.onMousedown, - onEnter: options.onEnter, - onPaste: options.onPaste, - onBeforeCommand: options.onBeforeCommand, + layoutInfo.editable.data('callbacks', { onChange: options.onChange, + onAutoSave: options.onAutoSave, onImageUpload: options.onImageUpload, onImageUploadError: options.onImageUploadError, - onMediaDelete: options.onMediaDelete, - onToolbarClick: options.onToolbarClick + onFileUpload: options.onFileUpload, + onFileUploadError: options.onFileUpload }); - - var styleInfo = modules.editor.styleFromNode(layoutInfo.editable()); - this.updateStyleInfo(styleInfo, layoutInfo); }; - /** - * attach jquery custom event - * - * @param {Object} layoutInfo - layout Informations - */ - this.attachCustomEvent = function (layoutInfo, options) { - var $holder = layoutInfo.holder(); - var $editable = layoutInfo.editable(); - var callbacks = $editable.data('callbacks'); - - $editable.focus(bindCustomEvent($holder, callbacks, 'focus')); - $editable.blur(bindCustomEvent($holder, callbacks, 'blur')); - - $editable.keydown(function (event) { - if (event.keyCode === key.code.ENTER) { - bindCustomEvent($holder, callbacks, 'enter').call(this, event); - } - bindCustomEvent($holder, callbacks, 'keydown').call(this, event); - }); - $editable.keyup(bindCustomEvent($holder, callbacks, 'keyup')); - - $editable.on('mousedown', bindCustomEvent($holder, callbacks, 'mousedown')); - $editable.on('mouseup', bindCustomEvent($holder, callbacks, 'mouseup')); - $editable.on('scroll', bindCustomEvent($holder, callbacks, 'scroll')); - - $editable.on('paste', bindCustomEvent($holder, callbacks, 'paste')); - - // [workaround] IE doesn't have input events for contentEditable - // - see: https://goo.gl/4bfIvA - var changeEventName = agent.isMSIE ? 'DOMCharacterDataModified DOMSubtreeModified DOMNodeInserted' : 'input'; - $editable.on(changeEventName, function () { - bindCustomEvent($holder, callbacks, 'change')($editable.html(), $editable); - }); - - if (!options.airMode) { - layoutInfo.toolbar().click(bindCustomEvent($holder, callbacks, 'toolbar.click')); - layoutInfo.popover().click(bindCustomEvent($holder, callbacks, 'popover.click')); - } - - // Textarea: auto filling the code before form submit. - if (dom.isTextarea(list.head($holder))) { - $holder.closest('form').submit(function (e) { - layoutInfo.holder().val(layoutInfo.holder().code()); - bindCustomEvent($holder, callbacks, 'submit').call(this, e, $holder.code()); - }); - } - - // textarea auto sync - if (dom.isTextarea(list.head($holder)) && options.textareaAutoSync) { - $holder.on('summernote.change', function () { - layoutInfo.holder().val(layoutInfo.holder().code()); - }); - } - - // fire init event - bindCustomEvent($holder, callbacks, 'init')(layoutInfo); - - // fire plugin init event - for (var i = 0, len = $.summernote.plugins.length; i < len; i++) { - if ($.isFunction($.summernote.plugins[i].init)) { - $.summernote.plugins[i].init(layoutInfo); - } - } - }; - this.detach = function (layoutInfo, options) { - layoutInfo.holder().off(); - layoutInfo.editable().off(); + layoutInfo.editable.off(); - layoutInfo.popover().off(); - layoutInfo.handle().off(); - layoutInfo.dialog().off(); + layoutInfo.popover.off(); + layoutInfo.handle.off(); + layoutInfo.dialog.off(); if (!options.airMode) { - layoutInfo.dropzone().off(); - layoutInfo.toolbar().off(); - layoutInfo.statusbar().off(); + layoutInfo.dropzone.off(); + layoutInfo.toolbar.off(); + layoutInfo.statusbar.off(); } }; }; diff --git a/addons/web_editor/static/lib/summernote/src/js/Renderer.js b/addons/web_editor/static/lib/summernote/src/js/Renderer.js index 922bd6a111c..07584edc453 100644 --- a/addons/web_editor/static/lib/summernote/src/js/Renderer.js +++ b/addons/web_editor/static/lib/summernote/src/js/Renderer.js @@ -1,12 +1,7 @@ define([ - 'summernote/core/agent', - 'summernote/core/dom', - 'summernote/core/func', - 'summernote/core/list' -], function (agent, dom, func, list) { + 'summernote/core/agent', 'summernote/core/dom', 'summernote/core/func' +], function (agent, dom, func) { /** - * @class Renderer - * * renderer * * rendering toolbar and editable @@ -15,15 +10,14 @@ define([ /** * bootstrap button template - * @private - * @param {String} label button name - * @param {Object} [options] button options - * @param {String} [options.event] data-event - * @param {String} [options.className] button's class name - * @param {String} [options.value] data-value - * @param {String} [options.title] button's title for popup - * @param {String} [options.dropdown] dropdown html - * @param {String} [options.hide] data-hide + * + * @param {String} label + * @param {Object} [options] + * @param {String} [options.event] + * @param {String} [options.value] + * @param {String} [options.title] + * @param {String} [options.dropdown] + * @param {String} [options.hide] */ var tplButton = function (label, options) { var event = options.event; @@ -33,11 +27,9 @@ define([ var dropdown = options.dropdown; var hide = options.hide; - return (dropdown ? '<div class="btn-group' + - (className ? ' ' + className : '') + '">' : '') + - '<button type="button"' + - ' class="btn btn-default btn-sm' + - ((!dropdown && className) ? ' ' + className : '') + + return '<button type="button"' + + ' class="btn btn-default btn-sm btn-small' + + (className ? ' ' + className : '') + (dropdown ? ' dropdown-toggle' : '') + '"' + (dropdown ? ' data-toggle="dropdown"' : '') + @@ -46,16 +38,15 @@ define([ (value ? ' data-value=\'' + value + '\'' : '') + (hide ? ' data-hide=\'' + hide + '\'' : '') + ' tabindex="-1">' + - label + - (dropdown ? ' <span class="caret"></span>' : '') + - '</button>' + - (dropdown || '') + - (dropdown ? '</div>' : ''); + label + + (dropdown ? ' <span class="caret"></span>' : '') + + '</button>' + + (dropdown || ''); }; /** * bootstrap icon button template - * @private + * * @param {String} iconClassName * @param {Object} [options] * @param {String} [options.event] @@ -70,28 +61,26 @@ define([ /** * bootstrap popover template - * @private + * * @param {String} className * @param {String} content */ var tplPopover = function (className, content) { - var $popover = $('<div class="' + className + ' popover bottom in" style="display: none;">' + + return '<div class="' + className + ' popover bottom in" style="display: none;">' + '<div class="arrow"></div>' + '<div class="popover-content">' + + content + '</div>' + - '</div>'); - - $popover.find('.popover-content').append(content); - return $popover; + '</div>'; }; /** * bootstrap dialog template * * @param {String} className - * @param {String} [title=''] + * @param {String} [title] * @param {String} body - * @param {String} [footer=''] + * @param {String} [footer] */ var tplDialog = function (className, title, body, footer) { return '<div class="' + className + ' modal" aria-hidden="false">' + @@ -103,60 +92,44 @@ define([ '<h4 class="modal-title">' + title + '</h4>' + '</div>' : '' ) + - '<div class="modal-body">' + body + '</div>' + - (footer ? - '<div class="modal-footer">' + footer + '</div>' : '' - ) + + '<form class="note-modal-form">' + + '<div class="modal-body">' + body + '</div>' + + (footer ? + '<div class="modal-footer">' + footer + '</div>' : '' + ) + + '</form>' + '</div>' + '</div>' + '</div>'; }; - /** - * bootstrap dropdown template - * - * @param {String|String[]} contents - * @param {String} [className=''] - * @param {String} [nodeName=''] - */ - var tplDropdown = function (contents, className, nodeName) { - var classes = 'dropdown-menu' + (className ? ' ' + className : ''); - nodeName = nodeName || 'ul'; - if (contents instanceof Array) { - contents = contents.join(''); - } - - return '<' + nodeName + ' class="' + classes + '">' + contents + '</' + nodeName + '>'; - }; - var tplButtonInfo = { - picture: function (lang, options) { - return tplIconButton(options.iconPrefix + options.icons.image.image, { + picture: function (lang) { + return tplIconButton('fa fa-picture-o', { event: 'showImageDialog', title: lang.image.image, hide: true }); }, - link: function (lang, options) { - return tplIconButton(options.iconPrefix + options.icons.link.link, { + link: function (lang) { + return tplIconButton('fa fa-link', { event: 'showLinkDialog', title: lang.link.link, hide: true }); }, - table: function (lang, options) { - var dropdown = [ - '<div class="note-dimension-picker">', - '<div class="note-dimension-picker-mousecatcher" data-event="insertTable" data-value="1x1"></div>', - '<div class="note-dimension-picker-highlighted"></div>', - '<div class="note-dimension-picker-unhighlighted"></div>', - '</div>', - '<div class="note-dimension-display"> 1 x 1 </div>' - ]; - - return tplIconButton(options.iconPrefix + options.icons.table.table, { + table: function (lang) { + var dropdown = '<ul class="note-table dropdown-menu">' + + '<div class="note-dimension-picker">' + + '<div class="note-dimension-picker-mousecatcher" data-event="insertTable" data-value="1x1"></div>' + + '<div class="note-dimension-picker-highlighted"></div>' + + '<div class="note-dimension-picker-unhighlighted"></div>' + + '</div>' + + '<div class="note-dimension-display"> 1 x 1 </div>' + + '</ul>'; + return tplIconButton('fa fa-table', { title: lang.table.table, - dropdown: tplDropdown(dropdown, 'note-table') + dropdown: dropdown }); }, style: function (lang, options) { @@ -170,54 +143,28 @@ define([ '</a></li>'; }, ''); - return tplIconButton(options.iconPrefix + options.icons.style.style, { + return tplIconButton('fa fa-magic', { title: lang.style.style, - dropdown: tplDropdown(items) + dropdown: '<ul class="dropdown-menu">' + items + '</ul>' }); }, fontname: function (lang, options) { - var realFontList = []; var items = options.fontNames.reduce(function (memo, v) { - if (!agent.isFontInstalled(v) && !list.contains(options.fontNamesIgnoreCheck, v)) { - return memo; - } - realFontList.push(v); - return memo + '<li><a data-event="fontName" href="#" data-value="' + v + '" style="font-family:\'' + v + '\'">' + - '<i class="' + options.iconPrefix + options.icons.misc.check + '"></i> ' + v + + if (!agent.isFontInstalled(v)) { return memo; } + return memo + '<li><a data-event="fontName" href="#" data-value="' + v + '">' + + '<i class="fa fa-check"></i> ' + v + '</a></li>'; }, ''); - - var hasDefaultFont = agent.isFontInstalled(options.defaultFontName); - var defaultFontName = (hasDefaultFont) ? options.defaultFontName : realFontList[0]; - var label = '<span class="note-current-fontname">' + - defaultFontName + + options.defaultFontName + '</span>'; return tplButton(label, { title: lang.font.name, - className: 'note-fontname', - dropdown: tplDropdown(items, 'note-check') - }); - }, - fontsize: function (lang, options) { - var items = options.fontSizes.reduce(function (memo, v) { - return memo + '<li><a data-event="fontSize" href="#" data-value="' + v + '">' + - '<i class="' + options.iconPrefix + options.icons.misc.check + '"></i> ' + v + - '</a></li>'; - }, ''); - - var label = '<span class="note-current-fontsize">11</span>'; - return tplButton(label, { - title: lang.font.size, - className: 'note-fontsize', - dropdown: tplDropdown(items, 'note-check') + dropdown: '<ul class="dropdown-menu">' + items + '</ul>' }); }, - color: function (lang, options) { - var colorButtonLabel = '<i class="' + - options.iconPrefix + options.icons.color.recent + - '" style="color:black;background-color:yellow;"></i>'; - + color: function (lang) { + var colorButtonLabel = '<i class="fa fa-font" style="color:black;background-color:yellow;"></i>'; var colorButton = tplButton(colorButtonLabel, { className: 'note-recent-color', title: lang.color.recent, @@ -225,168 +172,156 @@ define([ value: '{"backColor":"yellow"}' }); - var items = [ - '<li><div class="btn-group">', - '<div class="note-palette-title">' + lang.color.background + '</div>', - '<div class="note-color-reset" data-event="backColor"', - ' data-value="inherit" title="' + lang.color.transparent + '">' + lang.color.setTransparent + '</div>', - '<div class="note-color-palette" data-target-event="backColor"></div>', - '</div><div class="btn-group">', - '<div class="note-palette-title">' + lang.color.foreground + '</div>', - '<div class="note-color-reset" data-event="foreColor" data-value="inherit" title="' + lang.color.reset + '">', - lang.color.resetToDefault, - '</div>', - '<div class="note-color-palette" data-target-event="foreColor"></div>', - '</div></li>' - ]; + var dropdown = '<ul class="dropdown-menu">' + + '<li>' + + '<div class="btn-group">' + + '<div class="note-palette-title">' + lang.color.background + '</div>' + + '<div class="note-color-reset" data-event="backColor"' + + ' data-value="inherit" title="' + lang.color.transparent + '">' + + lang.color.setTransparent + + '</div>' + + '<div class="note-color-palette" data-target-event="backColor"></div>' + + '</div>' + + '<div class="btn-group">' + + '<div class="note-palette-title">' + lang.color.foreground + '</div>' + + '<div class="note-color-reset" data-event="foreColor" data-value="inherit" title="' + lang.color.reset + '">' + + lang.color.resetToDefault + + '</div>' + + '<div class="note-color-palette" data-target-event="foreColor"></div>' + + '</div>' + + '</li>' + + '</ul>'; var moreButton = tplButton('', { title: lang.color.more, - dropdown: tplDropdown(items) + dropdown: dropdown }); return colorButton + moreButton; }, - bold: function (lang, options) { - return tplIconButton(options.iconPrefix + options.icons.font.bold, { + bold: function (lang) { + return tplIconButton('fa fa-bold', { event: 'bold', title: lang.font.bold }); }, - italic: function (lang, options) { - return tplIconButton(options.iconPrefix + options.icons.font.italic, { + italic: function (lang) { + return tplIconButton('fa fa-italic', { event: 'italic', title: lang.font.italic }); }, - underline: function (lang, options) { - return tplIconButton(options.iconPrefix + options.icons.font.underline, { + underline: function (lang) { + return tplIconButton('fa fa-underline', { event: 'underline', title: lang.font.underline }); }, - strikethrough: function (lang, options) { - return tplIconButton(options.iconPrefix + options.icons.font.strikethrough, { - event: 'strikethrough', - title: lang.font.strikethrough - }); - }, - superscript: function (lang, options) { - return tplIconButton(options.iconPrefix + options.icons.font.superscript, { - event: 'superscript', - title: lang.font.superscript - }); - }, - subscript: function (lang, options) { - return tplIconButton(options.iconPrefix + options.icons.font.subscript, { - event: 'subscript', - title: lang.font.subscript - }); - }, - clear: function (lang, options) { - return tplIconButton(options.iconPrefix + options.icons.font.clear, { + clear: function (lang) { + return tplIconButton('fa fa-eraser', { event: 'removeFormat', title: lang.font.clear }); }, - ul: function (lang, options) { - return tplIconButton(options.iconPrefix + options.icons.lists.unordered, { + ul: function (lang) { + return tplIconButton('fa fa-list-ul', { event: 'insertUnorderedList', title: lang.lists.unordered }); }, - ol: function (lang, options) { - return tplIconButton(options.iconPrefix + options.icons.lists.ordered, { + ol: function (lang) { + return tplIconButton('fa fa-list-ol', { event: 'insertOrderedList', title: lang.lists.ordered }); }, - paragraph: function (lang, options) { - var leftButton = tplIconButton(options.iconPrefix + options.icons.paragraph.left, { + paragraph: function (lang) { + var leftButton = tplIconButton('fa fa-align-left', { title: lang.paragraph.left, event: 'justifyLeft' }); - var centerButton = tplIconButton(options.iconPrefix + options.icons.paragraph.center, { + var centerButton = tplIconButton('fa fa-align-center', { title: lang.paragraph.center, event: 'justifyCenter' }); - var rightButton = tplIconButton(options.iconPrefix + options.icons.paragraph.right, { + var rightButton = tplIconButton('fa fa-align-right', { title: lang.paragraph.right, event: 'justifyRight' }); - var justifyButton = tplIconButton(options.iconPrefix + options.icons.paragraph.justify, { + var justifyButton = tplIconButton('fa fa-align-justify', { title: lang.paragraph.justify, event: 'justifyFull' }); - var outdentButton = tplIconButton(options.iconPrefix + options.icons.paragraph.outdent, { + var outdentButton = tplIconButton('fa fa-outdent', { title: lang.paragraph.outdent, event: 'outdent' }); - var indentButton = tplIconButton(options.iconPrefix + options.icons.paragraph.indent, { + var indentButton = tplIconButton('fa fa-indent', { title: lang.paragraph.indent, event: 'indent' }); - var dropdown = [ - '<div class="note-align btn-group">', - leftButton + centerButton + rightButton + justifyButton, - '</div><div class="note-list btn-group">', - indentButton + outdentButton, - '</div>' - ]; + var dropdown = '<div class="dropdown-menu">' + + '<div class="note-align btn-group">' + + leftButton + centerButton + rightButton + justifyButton + + '</div>' + + '<div class="note-list btn-group">' + + indentButton + outdentButton + + '</div>' + + '</div>'; - return tplIconButton(options.iconPrefix + options.icons.paragraph.paragraph, { + return tplIconButton('fa fa-align-left', { title: lang.paragraph.paragraph, - dropdown: tplDropdown(dropdown, '', 'div') + dropdown: dropdown }); }, height: function (lang, options) { var items = options.lineHeights.reduce(function (memo, v) { return memo + '<li><a data-event="lineHeight" href="#" data-value="' + parseFloat(v) + '">' + - '<i class="' + options.iconPrefix + options.icons.misc.check + '"></i> ' + v + + '<i class="fa fa-check"></i> ' + v + '</a></li>'; }, ''); - return tplIconButton(options.iconPrefix + options.icons.font.height, { + return tplIconButton('fa fa-text-height', { title: lang.font.height, - dropdown: tplDropdown(items, 'note-check') + dropdown: '<ul class="dropdown-menu">' + items + '</ul>' }); }, - help: function (lang, options) { - return tplIconButton(options.iconPrefix + options.icons.options.help, { + help: function (lang) { + return tplIconButton('fa fa-question', { event: 'showHelpDialog', title: lang.options.help, hide: true }); }, - fullscreen: function (lang, options) { - return tplIconButton(options.iconPrefix + options.icons.options.fullscreen, { + fullscreen: function (lang) { + return tplIconButton('fa fa-arrows-alt', { event: 'fullscreen', title: lang.options.fullscreen }); }, - codeview: function (lang, options) { - return tplIconButton(options.iconPrefix + options.icons.options.codeview, { + codeview: function (lang) { + return tplIconButton('fa fa-code', { event: 'codeview', title: lang.options.codeview }); }, - undo: function (lang, options) { - return tplIconButton(options.iconPrefix + options.icons.history.undo, { + undo: function (lang) { + return tplIconButton('fa fa-undo', { event: 'undo', title: lang.history.undo }); }, - redo: function (lang, options) { - return tplIconButton(options.iconPrefix + options.icons.history.redo, { + redo: function (lang) { + return tplIconButton('fa fa-repeat', { event: 'redo', title: lang.history.redo }); }, - hr: function (lang, options) { - return tplIconButton(options.iconPrefix + options.icons.hr.insert, { + hr: function (lang) { + return tplIconButton('fa fa-minus', { event: 'insertHorizontalRule', title: lang.hr.insert }); @@ -395,12 +330,12 @@ define([ var tplPopovers = function (lang, options) { var tplLinkPopover = function () { - var linkButton = tplIconButton(options.iconPrefix + options.icons.link.edit, { + var linkButton = tplIconButton('fa fa-edit', { title: lang.link.edit, event: 'showLinkDialog', hide: true }); - var unlinkButton = tplIconButton(options.iconPrefix + options.icons.link.unlink, { + var unlinkButton = tplIconButton('fa fa-unlink', { title: lang.link.unlink, event: 'unlink' }); @@ -428,101 +363,89 @@ define([ value: '0.25' }); - var leftButton = tplIconButton(options.iconPrefix + options.icons.image.floatLeft, { + var leftButton = tplIconButton('fa fa-align-left', { title: lang.image.floatLeft, event: 'floatMe', value: 'left' }); - var rightButton = tplIconButton(options.iconPrefix + options.icons.image.floatRight, { + var rightButton = tplIconButton('fa fa-align-right', { title: lang.image.floatRight, event: 'floatMe', value: 'right' }); - var justifyButton = tplIconButton(options.iconPrefix + options.icons.image.floatNone, { + var justifyButton = tplIconButton('fa fa-align-justify', { title: lang.image.floatNone, event: 'floatMe', value: 'none' }); - var roundedButton = tplIconButton(options.iconPrefix + options.icons.image.shapeRounded, { + var roundedButton = tplIconButton('fa fa-square', { title: lang.image.shapeRounded, event: 'imageShape', value: 'img-rounded' }); - var circleButton = tplIconButton(options.iconPrefix + options.icons.image.shapeCircle, { + var circleButton = tplIconButton('fa fa-circle', { title: lang.image.shapeCircle, event: 'imageShape', value: 'img-circle' }); - var thumbnailButton = tplIconButton(options.iconPrefix + options.icons.image.shapeThumbnail, { + var thumbnailButton = tplIconButton('fa fa-picture-o', { title: lang.image.shapeThumbnail, event: 'imageShape', value: 'img-thumbnail' }); - var noneButton = tplIconButton(options.iconPrefix + options.icons.image.shapeNone, { + var noneButton = tplIconButton('fa fa-times', { title: lang.image.shapeNone, event: 'imageShape', value: '' }); - var removeButton = tplIconButton(options.iconPrefix + options.icons.image.remove, { + var removeButton = tplIconButton('fa fa-trash-o', { title: lang.image.remove, event: 'removeMedia', value: 'none' }); - var content = (options.disableResizeImage ? '' : '<div class="btn-group">' + fullButton + halfButton + quarterButton + '</div>') + - '<div class="btn-group">' + leftButton + rightButton + justifyButton + '</div><br>' + + var content = '<div class="btn-group">' + fullButton + halfButton + quarterButton + '</div>' + + '<div class="btn-group">' + leftButton + rightButton + justifyButton + '</div>' + '<div class="btn-group">' + roundedButton + circleButton + thumbnailButton + noneButton + '</div>' + '<div class="btn-group">' + removeButton + '</div>'; return tplPopover('note-image-popover', content); }; var tplAirPopover = function () { - var $content = $('<div />'); + var content = ''; for (var idx = 0, len = options.airPopover.length; idx < len; idx ++) { var group = options.airPopover[idx]; - - var $group = $('<div class="note-' + group[0] + ' btn-group">'); + content += '<div class="note-' + group[0] + ' btn-group">'; for (var i = 0, lenGroup = group[1].length; i < lenGroup; i++) { - var $button = $(tplButtonInfo[group[1][i]](lang, options)); - - $button.attr('data-name', group[1][i]); - - $group.append($button); + content += tplButtonInfo[group[1][i]](lang, options); } - $content.append($group); + content += '</div>'; } - return tplPopover('note-air-popover', $content.children()); + return tplPopover('note-air-popover', content); }; - var $notePopover = $('<div class="note-popover" />'); - - $notePopover.append(tplLinkPopover()); - $notePopover.append(tplImagePopover()); - - if (options.airMode) { - $notePopover.append(tplAirPopover()); - } - - return $notePopover; + return '<div class="note-popover">' + + tplLinkPopover() + + tplImagePopover() + + (options.airMode ? tplAirPopover() : '') + + '</div>'; }; - this.tplButtonInfo = tplButtonInfo; // ODOO: allow access for override - this.tplPopovers = tplPopovers; // ODOO: allow access for override + this.tplButtonInfo = tplButtonInfo; // odoo change for overwrite + this.tplPopovers = tplPopovers; // odoo change for overwrite - var tplHandles = function (options) { + var tplHandles = function () { return '<div class="note-handle">' + '<div class="note-control-selection">' + '<div class="note-control-selection-bg"></div>' + '<div class="note-control-holder note-control-nw"></div>' + '<div class="note-control-holder note-control-ne"></div>' + '<div class="note-control-holder note-control-sw"></div>' + - '<div class="' + - (options.disableResizeImage ? 'note-control-holder' : 'note-control-sizing') + - ' note-control-se"></div>' + - (options.disableResizeImage ? '' : '<div class="note-control-selection-info"></div>') + + '<div class="note-control-sizing note-control-se"></div>' + + '<div class="note-control-selection-info"></div>' + '</div>' + '</div>'; }; @@ -554,6 +477,7 @@ define([ { kbd: '⌘ + B', text: lang.font.bold }, { kbd: '⌘ + I', text: lang.font.italic }, { kbd: '⌘ + U', text: lang.font.underline }, + { kbd: '⌘ + ⇧ + S', text: lang.font.sdivikethrough }, { kbd: '⌘ + \\', text: lang.font.clear } ]; @@ -565,7 +489,7 @@ define([ { kbd: '⌘ + Z', text: lang.history.undo }, { kbd: '⌘ + ⇧ + Z', text: lang.history.redo }, { kbd: '⌘ + ]', text: lang.paragraph.indent }, - { kbd: '⌘ + [', text: lang.paragraph.outdent }, + { kbd: '⌘ + [', text: lang.paragraph.oudivent }, { kbd: '⌘ + ENTER', text: lang.hr.insert } ]; @@ -644,27 +568,27 @@ define([ imageLimitation = '<small>' + lang.image.maximumFileSize + ' : ' + readableSize + '</small>'; } - var body = '<div class="form-group row note-group-select-from-files">' + + var body = '<div class="form-group row-fluid note-group-select-from-files">' + '<label>' + lang.image.selectFromFiles + '</label>' + - '<input class="note-image-input form-control" type="file" name="files" accept="image/*" multiple="multiple" />' + + '<input class="note-image-input" type="file" name="files" accept="image/*" multiple="multiple" />' + imageLimitation + '</div>' + - '<div class="form-group row">' + + '<div class="form-group row-fluid">' + '<label>' + lang.image.url + '</label>' + - '<input class="note-image-url form-control col-md-12" type="text" />' + + '<input class="note-image-url form-control span12" type="text" />' + '</div>'; var footer = '<button href="#" class="btn btn-primary note-image-btn disabled" disabled>' + lang.image.insert + '</button>'; return tplDialog('note-image-dialog', lang.image.insert, body, footer); }, link: function (lang, options) { - var body = '<div class="form-group row">' + + var body = '<div class="form-group row-fluid">' + '<label>' + lang.link.textToDisplay + '</label>' + - '<input class="note-link-text form-control col-md-12" type="text" />' + + '<input class="note-link-text form-control span12" type="text" />' + '</div>' + - '<div class="form-group row">' + + '<div class="form-group row-fluid">' + '<label>' + lang.link.url + '</label>' + - '<input class="note-link-url form-control col-md-12" type="text" value="http://" />' + + '<input class="note-link-url form-control span12" type="text" />' + '</div>' + (!options.disableLinkTarget ? '<div class="checkbox">' + @@ -682,9 +606,9 @@ define([ '<div class="title">' + lang.shortcut.shortcuts + '</div>' + (agent.isMac ? tplShortcutTable(lang, options) : replaceMacKeys(tplShortcutTable(lang, options))) + '<p class="text-center">' + - '<a href="//summernote.org/" target="_blank">Summernote @VERSION</a> · ' + - '<a href="//github.com/summernote/summernote" target="_blank">Project</a> · ' + - '<a href="//github.com/summernote/summernote/issues" target="_blank">Issues</a>' + + '<a href="//hackerwins.github.io/summernote/" target="_blank">Summernote @VERSION</a> · ' + + '<a href="//github.com/HackerWins/summernote" target="_blank">Project</a> · ' + + '<a href="//github.com/HackerWins/summernote/issues" target="_blank">Issues</a>' + '</p>'; return tplDialog('note-help-dialog', '', body, ''); } @@ -772,7 +696,7 @@ define([ }); }; - this.createPalette = createPalette; // ODOO: allow access for override + this.createPalette = createPalette; // odoo change for overwrite /** * create summernote layout (air mode) @@ -785,25 +709,24 @@ define([ var keyMap = options.keyMap[agent.isMac ? 'mac' : 'pc']; var id = func.uniqueId(); - $holder.addClass('note-air-editor note-editable panel-body'); + $holder.addClass('note-air-editor note-editable'); $holder.attr({ - 'data-note-id': id, // ODOO: we use [data-note-id="{id}"] instead of [id="{id}"] - // 'id': 'note-editor-' + id, + 'data-note-id': id, 'contentEditable': true }); var body = document.body; // create Popover - var $popover = $(this.tplPopovers(langInfo, options)); // ODOO: user (maybe) overrided method + var $popover = $(this.tplPopovers(langInfo, options)); // odoo change for overwrite $popover.addClass('note-air-layout'); $popover.attr('id', 'note-popover-' + id); $popover.appendTo(body); createTooltip($popover, keyMap); - this.createPalette($popover, options); // ODOO: use (maybe) overrided method + this.createPalette($popover, options); // odoo change for overwrite // create Handle - var $handle = $(tplHandles(options)); + var $handle = $(tplHandles()); $handle.addClass('note-air-layout'); $handle.attr('id', 'note-handle-' + id); $handle.appendTo(body); @@ -828,7 +751,7 @@ define([ var langInfo = options.langInfo; //01. create Editor - var $editor = $('<div class="note-editor panel panel-default" />'); + var $editor = $('<div class="note-editor"></div>'); if (options.width) { $editor.width(options.width); } @@ -838,81 +761,70 @@ define([ $('<div class="note-statusbar">' + (options.disableResizeEditor ? '' : tplStatusbar()) + '</div>').prependTo($editor); } - //03 editing area - var $editingArea = $('<div class="note-editing-area" />'); - //03. create editable + //03. create Editable var isContentEditable = !$holder.is(':disabled'); - var $editable = $('<div class="note-editable panel-body" contentEditable="' + isContentEditable + '"></div>').prependTo($editingArea); - + var $editable = $('<div class="note-editable" contentEditable="' + isContentEditable + '"></div>') + .prependTo($editor); if (options.height) { $editable.height(options.height); } if (options.direction) { $editable.attr('dir', options.direction); } - var placeholder = $holder.attr('placeholder') || options.placeholder; - if (placeholder) { - $editable.attr('data-placeholder', placeholder); + if (options.placeholder) { + $editable.attr('data-placeholder', options.placeholder); } - $editable.html(dom.html($holder) || dom.emptyPara); + $editable.html(dom.html($holder)); //031. create codable - $('<textarea class="note-codable"></textarea>').prependTo($editingArea); - - //04. create Popover - var $popover = $(this.tplPopovers(langInfo, options)).prependTo($editingArea); // ODOO: use (maybe) overrided method - this.createPalette($popover, options); // ODOO: use (maybe) overrided method - createTooltip($popover, keyMap); - - //05. handle(control selection, ...) - $(tplHandles(options)).prependTo($editingArea); + $('<textarea class="note-codable"></textarea>').prependTo($editor); - $editingArea.prependTo($editor); - - //06. create Toolbar - var $toolbar = $('<div class="note-toolbar panel-heading" />'); + //04. create Toolbar + var toolbarHTML = ''; for (var idx = 0, len = options.toolbar.length; idx < len; idx ++) { var groupName = options.toolbar[idx][0]; var groupButtons = options.toolbar[idx][1]; - var $group = $('<div class="note-' + groupName + ' btn-group" />'); + toolbarHTML += '<div class="note-' + groupName + ' btn-group">'; for (var i = 0, btnLength = groupButtons.length; i < btnLength; i++) { var buttonInfo = tplButtonInfo[groupButtons[i]]; // continue creating toolbar even if a button doesn't exist if (!$.isFunction(buttonInfo)) { continue; } - - var $button = $(buttonInfo(langInfo, options)); - $button.attr('data-name', groupButtons[i]); // set button's alias, becuase to get button element from $toolbar - $group.append($button); + toolbarHTML += buttonInfo(langInfo, options); } - $toolbar.append($group); + toolbarHTML += '</div>'; } + toolbarHTML = '<div class="note-toolbar btn-toolbar">' + toolbarHTML + '</div>'; + + var $toolbar = $(toolbarHTML).prependTo($editor); var keyMap = options.keyMap[agent.isMac ? 'mac' : 'pc']; - this.createPalette($toolbar, options); // ODOO: use (maybe) overrided method + this.createPalette($toolbar, options); // odoo change for overwrite createTooltip($toolbar, keyMap, 'bottom'); - $toolbar.prependTo($editor); - //07. create Dropzone - $('<div class="note-dropzone"><div class="note-dropzone-message"></div></div>').prependTo($editor); + //05. create Popover + var $popover = $(this.tplPopovers(langInfo, options)).prependTo($editor); // odoo change for overwrite + this.createPalette($popover, options); // odoo change for overwrite + createTooltip($popover, keyMap); - //08. create Dialog - var $dialogContainer = options.dialogsInBody ? $(document.body) : $editor; - var $dialog = $(tplDialogs(langInfo, options)).prependTo($dialogContainer); + //06. handle(control selection, ...) + $(tplHandles()).prependTo($editor); + + //07. create Dialog + var $dialog = $(tplDialogs(langInfo, options)).prependTo($editor); $dialog.find('button.close, a.modal-close').click(function () { $(this).closest('.modal').modal('hide'); }); + //08. create Dropzone + $('<div class="note-dropzone"><div class="note-dropzone-message"></div></div>').prependTo($editor); + //09. Editor/Holder switch $editor.insertAfter($holder); $holder.hide(); }; - this.hasNoteEditor = function ($holder) { - return this.noteEditorFromHolder($holder).length > 0; - }; - this.noteEditorFromHolder = function ($holder) { if ($holder.hasClass('note-air-editor')) { return $holder; @@ -930,6 +842,10 @@ define([ * @param {Object} options */ this.createLayout = function ($holder, options) { + if (this.noteEditorFromHolder($holder).length) { + return; + } + if (options.airMode) { this.createLayoutByAirMode($holder, options); } else { @@ -941,18 +857,20 @@ define([ * returns layoutInfo from holder * * @param {jQuery} $holder - placeholder - * @return {Object} + * @returns {Object} */ this.layoutInfoFromHolder = function ($holder) { var $editor = this.noteEditorFromHolder($holder); - if (!$editor.length) { - return; - } - - // connect $holder to $editor - $editor.data('holder', $holder); + if (!$editor.length) { return; } - return dom.buildLayoutInfo($editor); + var layoutInfo = dom.buildLayoutInfo($editor); + // cache all properties. + for (var key in layoutInfo) { + if (layoutInfo.hasOwnProperty(key)) { + layoutInfo[key] = layoutInfo[key].call(); + } + } + return layoutInfo; }; /** @@ -966,29 +884,19 @@ define([ this.removeLayout = function ($holder, layoutInfo, options) { if (options.airMode) { $holder.removeClass('note-air-editor note-editable') - .removeAttr('contentEditable'); // ODOO: removed id 'id contentEditable' + .removeAttr('contentEditable'); - layoutInfo.popover().remove(); - layoutInfo.handle().remove(); - layoutInfo.dialog().remove(); + layoutInfo.popover.remove(); + layoutInfo.handle.remove(); + layoutInfo.dialog.remove(); } else { - $holder.html(layoutInfo.editable().html()); + $holder.html(layoutInfo.editable.html()); - if (options.dialogsInBody) { - layoutInfo.dialog().remove(); - } - layoutInfo.editor().remove(); + layoutInfo.editor.remove(); $holder.show(); } }; - /** - * - * @return {Object} - * @return {function(label, options=):string} return.button {@link #tplButton function to make text button} - * @return {function(iconClass, options=):string} return.iconButton {@link #tplIconButton function to make icon button} - * @return {function(className, title=, body=, footer=):string} return.dialog {@link #tplDialog function to make dialog} - */ this.getTemplate = function () { return { button: tplButton, @@ -997,21 +905,10 @@ define([ }; }; - /** - * add button information - * - * @param {String} name button name - * @param {Function} buttonInfo function to make button, reference to {@link #tplButton},{@link #tplIconButton} - */ this.addButtonInfo = function (name, buttonInfo) { tplButtonInfo[name] = buttonInfo; }; - /** - * - * @param {String} name - * @param {Function} dialogInfo function to make dialog, reference to {@link #tplDialog} - */ this.addDialogInfo = function (name, dialogInfo) { tplDialogInfo[name] = dialogInfo; }; diff --git a/addons/web_editor/static/lib/summernote/src/js/app.js b/addons/web_editor/static/lib/summernote/src/js/app.js index fba17afd588..0a93e94d7cc 100644 --- a/addons/web_editor/static/lib/summernote/src/js/app.js +++ b/addons/web_editor/static/lib/summernote/src/js/app.js @@ -1,8 +1,8 @@ require.config({ baseUrl: 'src/js', paths: { - jquery: '//code.jquery.com/jquery-1.11.3', - bootstrap: '//netdna.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min', + jquery: '//code.jquery.com/jquery-1.9.1.min', + bootstrap: '//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min', summernotevideo: '/../../plugin/summernote-ext-video', CodeMirror: '//cdnjs.cloudflare.com/ajax/libs/codemirror/3.20.0/codemirror', CodeMirrorXml: '//cdnjs.cloudflare.com/ajax/libs/codemirror/3.20.0/mode/xml/xml.min', diff --git a/addons/web_editor/static/lib/summernote/src/js/core/agent.js b/addons/web_editor/static/lib/summernote/src/js/core/agent.js index 21555a1191c..d0ba7ba1ef2 100644 --- a/addons/web_editor/static/lib/summernote/src/js/core/agent.js +++ b/addons/web_editor/static/lib/summernote/src/js/core/agent.js @@ -1,54 +1,51 @@ define(['jquery'], function ($) { - if (!Array.prototype.reduce) { + if ('function' !== typeof Array.prototype.reduce) { /** - * Array.prototype.reduce polyfill + * Array.prototype.reduce fallback * - * @param {Function} callback - * @param {Value} [initialValue] - * @return {Value} - * - * @see http://goo.gl/WNriQD + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce */ - Array.prototype.reduce = function (callback) { - var t = Object(this), len = t.length >>> 0, k = 0, value; - if (arguments.length === 2) { - value = arguments[1]; - } else { - while (k < len && !(k in t)) { - k++; - } - if (k >= len) { - throw new TypeError('Reduce of empty array with no initial value'); - } - value = t[k++]; + Array.prototype.reduce = function (callback, optInitialValue) { + var idx, value, length = this.length >>> 0, isValueSet = false; + if (1 < arguments.length) { + value = optInitialValue; + isValueSet = true; } - for (; k < len; k++) { - if (k in t) { - value = callback(value, t[k], k, t); + for (idx = 0; length > idx; ++idx) { + if (this.hasOwnProperty(idx)) { + if (isValueSet) { + value = callback(value, this[idx], idx, this); + } else { + value = this[idx]; + isValueSet = true; + } } } + if (!isValueSet) { + throw new TypeError('Reduce of empty array with no initial value'); + } return value; }; } if ('function' !== typeof Array.prototype.filter) { - /** - * Array.prototype.filter polyfill - * - * @param {Function} func - * @return {Array} - * - * @see http://goo.gl/T1KFnq - */ - Array.prototype.filter = function (func) { - var t = Object(this), len = t.length >>> 0; - + Array.prototype.filter = function (fun/*, thisArg*/) { + if (this === void 0 || this === null) { + throw new TypeError(); + } + + var t = Object(this); + var len = t.length >>> 0; + if (typeof fun !== 'function') { + throw new TypeError(); + } + var res = []; var thisArg = arguments.length >= 2 ? arguments[1] : void 0; for (var i = 0; i < len; i++) { if (i in t) { var val = t[i]; - if (func.call(thisArg, val, i, t)) { + if (fun.call(thisArg, val, i, t)) { res.push(val); } } @@ -58,52 +55,10 @@ define(['jquery'], function ($) { }; } - if (!Array.prototype.map) { - /** - * Array.prototype.map polyfill - * - * @param {Function} callback - * @return {Array} - * - * @see https://goo.gl/SMWaMK - */ - Array.prototype.map = function (callback, thisArg) { - var T, A, k; - if (this === null) { - throw new TypeError(' this is null or not defined'); - } - - var O = Object(this); - var len = O.length >>> 0; - if (typeof callback !== 'function') { - throw new TypeError(callback + ' is not a function'); - } - - if (arguments.length > 1) { - T = thisArg; - } - - A = new Array(len); - k = 0; - - while (k < len) { - var kValue, mappedValue; - if (k in O) { - kValue = O[k]; - mappedValue = callback.call(T, kValue, k, O); - A[k] = mappedValue; - } - k++; - } - return A; - }; - } - var isSupportAmd = typeof define === 'function' && define.amd; /** * returns whether font is installed or not. - * * @param {String} fontName * @return {Boolean} */ @@ -124,41 +79,13 @@ define(['jquery'], function ($) { return originalWidth !== width; }; - var userAgent = navigator.userAgent; - var isMSIE = /MSIE|Trident/i.test(userAgent); - var browserVersion; - if (isMSIE) { - var matches = /MSIE (\d+[.]\d+)/.exec(userAgent); - if (matches) { - browserVersion = parseFloat(matches[1]); - } - matches = /Trident\/.*rv:([0-9]{1,}[\.0-9]{0,})/.exec(userAgent); - if (matches) { - browserVersion = parseFloat(matches[1]); - } - } - /** - * @class core.agent - * * Object which check platform and agent - * - * @singleton - * @alternateClassName agent */ var agent = { - /** @property {Boolean} [isMac=false] true if this agent is Mac */ isMac: navigator.appVersion.indexOf('Mac') > -1, - /** @property {Boolean} [isMSIE=false] true if this agent is a Internet Explorer */ - isMSIE: isMSIE, - /** @property {Boolean} [isFF=false] true if this agent is a Firefox */ - isFF: /firefox/i.test(userAgent), - isWebkit: /webkit/i.test(userAgent), - /** @property {Boolean} [isSafari=false] true if this agent is a Safari */ - isSafari: /safari/i.test(userAgent), - /** @property {Float} browserVersion current browser version */ - browserVersion: browserVersion, - /** @property {String} jqueryVersion current jQuery version string */ + isMSIE: navigator.userAgent.indexOf('MSIE') > -1 || navigator.userAgent.indexOf('Trident') > -1, + isFF: navigator.userAgent.indexOf('Firefox') > -1, jqueryVersion: parseFloat($.fn.jquery), isSupportAmd: isSupportAmd, hasCodeMirror: isSupportAmd ? require.specified('CodeMirror') : !!window.CodeMirror, diff --git a/addons/web_editor/static/lib/summernote/src/js/core/async.js b/addons/web_editor/static/lib/summernote/src/js/core/async.js index 7bc23586e86..9df0c8e6895 100644 --- a/addons/web_editor/static/lib/summernote/src/js/core/async.js +++ b/addons/web_editor/static/lib/summernote/src/js/core/async.js @@ -1,16 +1,9 @@ define('summernote/core/async', function () { /** - * @class core.async - * * Async functions which returns `Promise` - * - * @singleton - * @alternateClassName async */ var async = (function () { /** - * @method readFileAsDataURL - * * read contents of file as representing URL * * @param {File} file @@ -31,30 +24,22 @@ define('summernote/core/async', function () { }; /** - * @method createImage - * * create `<image>` from url string * * @param {String} sUrl - * @param {String} filename * @return {Promise} - then: $image */ var createImage = function (sUrl, filename) { return $.Deferred(function (deferred) { - var $img = $('<img>'); - - $img.one('load', function () { - $img.off('error abort'); - deferred.resolve($img); + $('<img>').one('load', function () { + deferred.resolve($(this)); }).one('error abort', function () { - $img.off('load').detach(); - deferred.reject($img); + deferred.reject($(this).detach()); }).css({ display: 'none' - }).appendTo(document.body).attr({ - 'src': sUrl, - 'data-filename': filename - }); + }).appendTo(document.body) + .attr('src', sUrl) + .attr('data-filename', filename); }).promise(); }; diff --git a/addons/web_editor/static/lib/summernote/src/js/core/dom.js b/addons/web_editor/static/lib/summernote/src/js/core/dom.js index 2250b50ac2a..ba55ae8fa01 100644 --- a/addons/web_editor/static/lib/summernote/src/js/core/dom.js +++ b/addons/web_editor/static/lib/summernote/src/js/core/dom.js @@ -8,17 +8,10 @@ define([ var ZERO_WIDTH_NBSP_CHAR = '\ufeff'; /** - * @class core.dom - * * Dom functions - * - * @singleton - * @alternateClassName dom */ var dom = (function () { /** - * @method isEditable - * * returns whether node is `note-editable` or not. * * @param {Node} node @@ -29,8 +22,6 @@ define([ }; /** - * @method isControlSizing - * * returns whether node is `note-control-sizing` or not. * * @param {Node} node @@ -41,28 +32,16 @@ define([ }; /** - * @method buildLayoutInfo - * * build layoutInfo from $editor(.note-editor) * * @param {jQuery} $editor * @return {Object} - * @return {Function} return.editor - * @return {Node} return.dropzone - * @return {Node} return.toolbar - * @return {Node} return.editable - * @return {Node} return.codable - * @return {Node} return.popover - * @return {Node} return.handle - * @return {Node} return.dialog */ var buildLayoutInfo = function ($editor) { var makeFinder; // air mode if ($editor.hasClass('note-air-editor')) { - // ODOO: editor on [data-note-id] attribute - // var id = list.last($editor.attr('id').split('-')); var id = list.last($editor.attr('data-note-id').split('-')); makeFinder = function (sIdPrefix) { return function () { return $(sIdPrefix + id); }; @@ -70,7 +49,6 @@ define([ return { editor: function () { return $editor; }, - holder : function () { return $editor.data('holder'); }, editable: function () { return $editor; }, popover: makeFinder('#note-popover-'), handle: makeFinder('#note-handle-'), @@ -79,17 +57,11 @@ define([ // frame mode } else { - makeFinder = function (className, $base) { - $base = $base || $editor; - return function () { return $base.find(className); }; + makeFinder = function (sClassName) { + return function () { return $editor.find(sClassName); }; }; - - var options = $editor.data('options'); - var $dialogHolder = (options && options.dialogsInBody) ? $(document.body) : null; - return { editor: function () { return $editor; }, - holder : function () { return $editor.data('holder'); }, dropzone: makeFinder('.note-dropzone'), toolbar: makeFinder('.note-toolbar'), editable: makeFinder('.note-editable'), @@ -97,44 +69,16 @@ define([ statusbar: makeFinder('.note-statusbar'), popover: makeFinder('.note-popover'), handle: makeFinder('.note-handle'), - dialog: makeFinder('.note-dialog', $dialogHolder) + dialog: makeFinder('.note-dialog') }; } }; /** - * returns makeLayoutInfo from editor's descendant node. - * - * @private - * @param {Node} descendant - * @return {Object} - */ - var makeLayoutInfo = function (descendant) { - var $target = $(descendant).closest('.note-editor, .note-air-editor, .note-air-layout'); - - if (!$target.length) { - return null; - } - - var $editor; - if ($target.is('.note-editor, .note-air-editor')) { - $editor = $target; - } else { - // ODOO: editor on [data-note-id] attribute - // $editor = $('#note-editor-' + list.last($target.attr('id').split('-'))); - $editor = $('[data-note-id="' + list.last($target.attr('id')) + '"]'); - } - - return buildLayoutInfo($editor); - }; - - /** - * @method makePredByNodeName - * * returns predicate which judge whether nodeName is same * * @param {String} nodeName - * @return {Function} + * @return {String} */ var makePredByNodeName = function (nodeName) { nodeName = nodeName.toUpperCase(); @@ -143,14 +87,6 @@ define([ }; }; - /** - * @method isText - * - * - * - * @param {Node} node - * @return {Boolean} true if node's type is text(3) - */ var isText = function (node) { return node && node.nodeType === 3; }; @@ -160,7 +96,7 @@ define([ * @see http://www.w3.org/html/wg/drafts/html/master/syntax.html#void-elements */ var isVoid = function (node) { - return node && /^BR|^IMG|^HR|^IFRAME|^BUTTON/.test(node.nodeName.toUpperCase()); + return node && /^BR|^IMG|^HR/.test(node.nodeName.toUpperCase()); }; var isPara = function (node) { @@ -178,23 +114,14 @@ define([ return isPara(node) && !isLi(node); }; - var isTable = makePredByNodeName('TABLE'); - var isInline = function (node) { - return !isBodyContainer(node) && - !isList(node) && - !isHr(node) && - !isPara(node) && - !isTable(node) && - !isBlockquote(node); + return !isBodyContainer(node) && !isList(node) && !isPara(node); }; var isList = function (node) { return node && /^UL|^OL/.test(node.nodeName.toUpperCase()); }; - var isHr = makePredByNodeName('HR'); - var isCell = function (node) { return node && /^TD|^TH/.test(node.nodeName.toUpperCase()); }; @@ -218,48 +145,11 @@ define([ var isBody = makePredByNodeName('BODY'); /** - * returns whether nodeB is closest sibling of nodeA - * - * @param {Node} nodeA - * @param {Node} nodeB - * @return {Boolean} - */ - var isClosestSibling = function (nodeA, nodeB) { - return nodeA.nextSibling === nodeB || - nodeA.previousSibling === nodeB; - }; - - /** - * returns array of closest siblings with node - * - * @param {Node} node - * @param {function} [pred] - predicate function - * @return {Node[]} - */ - var withClosestSiblings = function (node, pred) { - pred = pred || func.ok; - - var siblings = []; - if (node.previousSibling && pred(node.previousSibling)) { - siblings.push(node.previousSibling); - } - siblings.push(node); - if (node.nextSibling && pred(node.nextSibling)) { - siblings.push(node.nextSibling); - } - return siblings; - }; - - /** * blank HTML for cursor position - * - [workaround] old IE only works with - * - [workaround] IE11 and other browser works with bogus br */ - var blankHTML = agent.isMSIE && agent.browserVersion < 11 ? ' ' : '<br>'; + var blankHTML = agent.isMSIE ? ' ' : '<br>'; /** - * @method nodeLength - * * returns #text's text size or element's childNodes size * * @param {Node} node @@ -283,12 +173,9 @@ define([ if (len === 0) { return true; - } else if (!isText(node) && len === 1 && node.innerHTML === blankHTML) { + } else if (!dom.isText(node) && len === 1 && node.innerHTML === blankHTML) { // ex) <p><br></p>, <span><br></span> return true; - } else if (list.all(node.childNodes, isText) && node.innerHTML === '') { - // ex) <p></p>, <span></span> - return true; } return false; @@ -320,25 +207,6 @@ define([ }; /** - * find nearest ancestor only single child blood line and predicate hit - * - * @param {Node} node - * @param {Function} pred - predicate function - */ - var singleChildAncestor = function (node, pred) { - node = node.parentNode; - - while (node) { - if (nodeLength(node) !== 1) { break; } - if (pred(node)) { return node; } - if (isEditable(node)) { break; } - - node = node.parentNode; - } - return null; - }; - - /** * returns new array of ancestor nodes (until predicate hit). * * @param {Node} node @@ -552,26 +420,6 @@ define([ }; /** - * returns whether point is left edge of ancestor or not. - * @param {BoundaryPoint} point - * @param {Node} ancestor - * @return {Boolean} - */ - var isLeftEdgePointOf = function (point, ancestor) { - return isLeftEdgePoint(point) && isLeftEdgeOf(point.node, ancestor); - }; - - /** - * returns whether point is right edge of ancestor or not. - * @param {BoundaryPoint} point - * @param {Node} ancestor - * @return {Boolean} - */ - var isRightEdgePointOf = function (point, ancestor) { - return isRightEdgePoint(point) && isRightEdgeOf(point.node, ancestor); - }; - - /** * returns offset from parent. * * @param {Node} node @@ -682,8 +530,6 @@ define([ }; /** - * @method prevPointUtil - * * @param {BoundaryPoint} point * @param {Function} pred * @return {BoundaryPoint} @@ -701,8 +547,6 @@ define([ }; /** - * @method nextPointUntil - * * @param {BoundaryPoint} point * @param {Function} pred * @return {BoundaryPoint} @@ -720,23 +564,6 @@ define([ }; /** - * returns whether point has character or not. - * - * @param {Point} point - * @return {Boolean} - */ - var isCharPoint = function (point) { - if (!isText(point.node)) { - return false; - } - - var ch = point.node.nodeValue.charAt(point.offset - 1); - return ch && (ch !== ' ' && ch !== NBSP_CHAR); - }; - - /** - * @method walkPoint - * * @param {BoundaryPoint} startPoint * @param {BoundaryPoint} endPoint * @param {Function} handler @@ -760,8 +587,6 @@ define([ }; /** - * @method makeOffsetPath - * * return offsetPath(array of offset) from ancestor * * @param {Node} ancestor - ancestor node @@ -769,143 +594,93 @@ define([ */ var makeOffsetPath = function (ancestor, node) { var ancestors = listAncestor(node, func.eq(ancestor)); - return ancestors.map(position).reverse(); + return $.map(ancestors, position).reverse(); }; /** - * @method fromOffsetPath - * * return element from offsetPath(array of offset) * * @param {Node} ancestor - ancestor node - * @param {array} offsets - offsetPath + * @param {array} aOffset - offsetPath */ - var fromOffsetPath = function (ancestor, offsets) { + var fromOffsetPath = function (ancestor, aOffset) { var current = ancestor; - for (var i = 0, len = offsets.length; i < len; i++) { - if (current.childNodes.length <= offsets[i]) { + for (var i = 0, len = aOffset.length; i < len; i++) { + if (current.childNodes.length <= aOffset[i]) { current = current.childNodes[current.childNodes.length - 1]; } else { - current = current.childNodes[offsets[i]]; + current = current.childNodes[aOffset[i]]; } } return current; }; /** - * @method splitNode - * * split element or #text * * @param {BoundaryPoint} point - * @param {Object} [options] - * @param {Boolean} [options.isSkipPaddingBlankHTML] - default: false - * @param {Boolean} [options.isNotSplitEdgePoint] - default: false + * @param {Boolean} [isSkipPaddingBlankHTML] * @return {Node} right node of boundaryPoint */ - var splitNode = function (point, options) { - var isSkipPaddingBlankHTML = options && options.isSkipPaddingBlankHTML; - var isNotSplitEdgePoint = options && options.isNotSplitEdgePoint; - - // edge case - if (isEdgePoint(point) && (isText(point.node) || isNotSplitEdgePoint)) { + var splitNode = function (point, isSkipPaddingBlankHTML) { + // split #text + if (isText(point.node)) { + // edge case if (isLeftEdgePoint(point)) { return point.node; } else if (isRightEdgePoint(point)) { return point.node.nextSibling; } - } - // split #text - if (isText(point.node)) { return point.node.splitText(point.offset); - } else { - var childNode = point.node.childNodes[point.offset]; - var clone = insertAfter(point.node.cloneNode(false), point.node); - appendChildNodes(clone, listNext(childNode)); + } - if (!isSkipPaddingBlankHTML) { - paddingBlankHTML(point.node); - paddingBlankHTML(clone); - } + // split element + var childNode = point.node.childNodes[point.offset]; + var clone = insertAfter(point.node.cloneNode(false), point.node); + appendChildNodes(clone, listNext(childNode)); - return clone; + if (!isSkipPaddingBlankHTML) { + paddingBlankHTML(point.node); + paddingBlankHTML(clone); } + + return clone; }; /** - * @method splitTree - * * split tree by point * * @param {Node} root - split root * @param {BoundaryPoint} point - * @param {Object} [options] - * @param {Boolean} [options.isSkipPaddingBlankHTML] - default: false - * @param {Boolean} [options.isNotSplitEdgePoint] - default: false + * @param {Boolean} [isSkipPaddingBlankHTML] * @return {Node} right node of boundaryPoint */ - var splitTree = function (root, point, options) { + var splitTree = function (root, point, isSkipPaddingBlankHTML) { // ex) [#text, <span>, <p>] var ancestors = listAncestor(point.node, func.eq(root)); if (!ancestors.length) { return null; } else if (ancestors.length === 1) { - return splitNode(point, options); + return splitNode(point, isSkipPaddingBlankHTML); } return ancestors.reduce(function (node, parent) { + var clone = insertAfter(parent.cloneNode(false), parent); + if (node === point.node) { - node = splitNode(point, options); + node = splitNode(point, isSkipPaddingBlankHTML); } - return splitNode({ - node: parent, - offset: node ? dom.position(node) : nodeLength(parent) - }, options); - }); - }; + appendChildNodes(clone, listNext(node)); - /** - * split point - * - * @param {Point} point - * @param {Boolean} isInline - * @return {Object} - */ - var splitPoint = function (point, isInline) { - // find splitRoot, container - // - inline: splitRoot is a child of paragraph - // - block: splitRoot is a child of bodyContainer - var pred = isInline ? isPara : isBodyContainer; - var ancestors = listAncestor(point.node, pred); - var topAncestor = list.last(ancestors) || point.node; - - var splitRoot, container; - if (pred(topAncestor)) { - splitRoot = ancestors[ancestors.length - 2]; - container = topAncestor; - } else { - splitRoot = topAncestor; - container = splitRoot.parentNode; - } - - // if splitRoot is exists, split with splitTree - var pivot = splitRoot && splitTree(splitRoot, point, { - isSkipPaddingBlankHTML: isInline, - isNotSplitEdgePoint: isInline + if (!isSkipPaddingBlankHTML) { + paddingBlankHTML(parent); + paddingBlankHTML(clone); + } + return clone; }); - - // if container is point.node, find pivot with point.offset - if (!pivot && container === point.node) { - pivot = point.node.childNodes[point.offset]; - } - - return { - rightNode: pivot, - container: container - }; }; var create = function (nodeName) { @@ -917,10 +692,7 @@ define([ }; /** - * @method remove - * * remove node, (isRemoveChild: remove child or not) - * * @param {Node} node * @param {Boolean} isRemoveChild */ @@ -945,8 +717,6 @@ define([ }; /** - * @method removeWhile - * * @param {Node} node * @param {Function} pred */ @@ -963,8 +733,6 @@ define([ }; /** - * @method replace - * * replace node with provided nodeName * * @param {Node} node @@ -992,27 +760,13 @@ define([ var isTextarea = makePredByNodeName('TEXTAREA'); /** - * @param {jQuery} $node - * @param {Boolean} [stripLinebreaks] - default: false - */ - var value = function ($node, stripLinebreaks) { - var val = isTextarea($node[0]) ? $node.val() : $node.html(); - if (stripLinebreaks) { - return val.replace(/[\n\r]/g, ''); - } - return val; - }; - - /** - * @method html - * - * get the HTML contents of node + * get the HTML contents of node * * @param {jQuery} $node * @param {Boolean} [isNewlineOnBlock] */ var html = function ($node, isNewlineOnBlock) { - var markup = value($node); + var markup = isTextarea($node[0]) ? $node.val() : $node.html(); if (isNewlineOnBlock) { var regexTag = /<(\/?)(\b(?!!)[^>\s]*)(.*?)(\s*\/?>)/g; @@ -1030,31 +784,30 @@ define([ return markup; }; + var value = function ($textarea) { + var val = $textarea.val(); + // strip line breaks + return val.replace(/[\n\r]/g, ''); + }; + return { - /** @property {String} NBSP_CHAR */ NBSP_CHAR: NBSP_CHAR, - /** @property {String} ZERO_WIDTH_NBSP_CHAR */ ZERO_WIDTH_NBSP_CHAR: ZERO_WIDTH_NBSP_CHAR, - /** @property {String} blank */ blank: blankHTML, - /** @property {String} emptyPara */ emptyPara: '<p>' + blankHTML + '</p>', - makePredByNodeName: makePredByNodeName, isEditable: isEditable, isControlSizing: isControlSizing, buildLayoutInfo: buildLayoutInfo, - makeLayoutInfo: makeLayoutInfo, isText: isText, isVoid: isVoid, isPara: isPara, isPurePara: isPurePara, isInline: isInline, - isBlock: func.not(isInline), isBodyInline: isBodyInline, isBody: isBody, isParaInline: isParaInline, isList: isList, - isTable: isTable, + isTable: makePredByNodeName('TABLE'), isCell: isCell, isBlockquote: isBlockquote, isBodyContainer: isBodyContainer, @@ -1071,26 +824,20 @@ define([ isTextarea: isTextarea, isEmpty: isEmpty, isEmptyAnchor: func.and(isAnchor, isEmpty), - isClosestSibling: isClosestSibling, - withClosestSiblings: withClosestSiblings, nodeLength: nodeLength, isLeftEdgePoint: isLeftEdgePoint, isRightEdgePoint: isRightEdgePoint, isEdgePoint: isEdgePoint, isLeftEdgeOf: isLeftEdgeOf, isRightEdgeOf: isRightEdgeOf, - isLeftEdgePointOf: isLeftEdgePointOf, - isRightEdgePointOf: isRightEdgePointOf, prevPoint: prevPoint, nextPoint: nextPoint, isSamePoint: isSamePoint, isVisiblePoint: isVisiblePoint, prevPointUntil: prevPointUntil, nextPointUntil: nextPointUntil, - isCharPoint: isCharPoint, walkPoint: walkPoint, ancestor: ancestor, - singleChildAncestor: singleChildAncestor, listAncestor: listAncestor, lastAncestor: lastAncestor, listNext: listNext, @@ -1105,7 +852,6 @@ define([ makeOffsetPath: makeOffsetPath, fromOffsetPath: fromOffsetPath, splitTree: splitTree, - splitPoint: splitPoint, create: create, createText: createText, remove: remove, diff --git a/addons/web_editor/static/lib/summernote/src/js/core/func.js b/addons/web_editor/static/lib/summernote/src/js/core/func.js index 11bfdf546e0..1acb9f98e97 100644 --- a/addons/web_editor/static/lib/summernote/src/js/core/func.js +++ b/addons/web_editor/static/lib/summernote/src/js/core/func.js @@ -1,11 +1,6 @@ define('summernote/core/func', function () { /** - * @class core.func - * * func utils (for high-order func's arg) - * - * @singleton - * @alternateClassName func */ var func = (function () { var eq = function (itemA) { @@ -98,18 +93,6 @@ define('summernote/core/func', function () { return inverted; }; - /** - * @param {String} namespace - * @param {String} [prefix] - * @return {String} - */ - var namespaceToCamel = function (namespace, prefix) { - prefix = prefix || ''; - return prefix + namespace.split('.').map(function (name) { - return name.substring(0, 1).toUpperCase() + name.substring(1); - }).join(''); - }; - return { eq: eq, eq2: eq2, @@ -121,8 +104,7 @@ define('summernote/core/func', function () { and: and, uniqueId: uniqueId, rect2bnd: rect2bnd, - invertObject: invertObject, - namespaceToCamel: namespaceToCamel + invertObject: invertObject }; })(); diff --git a/addons/web_editor/static/lib/summernote/src/js/core/key.js b/addons/web_editor/static/lib/summernote/src/js/core/key.js index a17b2756328..80631125bfa 100644 --- a/addons/web_editor/static/lib/summernote/src/js/core/key.js +++ b/addons/web_editor/static/lib/summernote/src/js/core/key.js @@ -1,96 +1,49 @@ define([ - 'summernote/core/list', - 'summernote/core/func' -], function (list, func) { + 'summernote/core/list' +], function (list) { /** - * @class core.key - * * Object for keycodes. - * - * @singleton - * @alternateClassName key */ - var key = (function () { - var keyMap = { - 'BACKSPACE': 8, - 'TAB': 9, - 'ENTER': 13, - 'SPACE': 32, - - // Arrow - 'LEFT': 37, - 'UP': 38, - 'RIGHT': 39, - 'DOWN': 40, + var key = { + isEdit: function (keyCode) { + return list.contains([8, 9, 13, 32], keyCode); + }, + nameFromCode: { + '8': 'BACKSPACE', + '9': 'TAB', + '13': 'ENTER', + '32': 'SPACE', // Number: 0-9 - 'NUM0': 48, - 'NUM1': 49, - 'NUM2': 50, - 'NUM3': 51, - 'NUM4': 52, - 'NUM5': 53, - 'NUM6': 54, - 'NUM7': 55, - 'NUM8': 56, + '48': 'NUM0', + '49': 'NUM1', + '50': 'NUM2', + '51': 'NUM3', + '52': 'NUM4', + '53': 'NUM5', + '54': 'NUM6', + '55': 'NUM7', + '56': 'NUM8', // Alphabet: a-z - 'B': 66, - 'E': 69, - 'I': 73, - 'J': 74, - 'K': 75, - 'L': 76, - 'R': 82, - 'S': 83, - 'U': 85, - 'V': 86, - 'Y': 89, - 'Z': 90, - - 'SLASH': 191, - 'LEFTBRACKET': 219, - 'BACKSLASH': 220, - 'RIGHTBRACKET': 221 - }; + '66': 'B', + '69': 'E', + '73': 'I', + '74': 'J', + '75': 'K', + '76': 'L', + '82': 'R', + '83': 'S', + '85': 'U', + '89': 'Y', + '90': 'Z', - return { - /** - * @method isEdit - * - * @param {Number} keyCode - * @return {Boolean} - */ - isEdit: function (keyCode) { - return list.contains([ - keyMap.BACKSPACE, - keyMap.TAB, - keyMap.ENTER, - keyMap.SPACe - ], keyCode); - }, - /** - * @method isMove - * - * @param {Number} keyCode - * @return {Boolean} - */ - isMove: function (keyCode) { - return list.contains([ - keyMap.LEFT, - keyMap.UP, - keyMap.RIGHT, - keyMap.DOWN - ], keyCode); - }, - /** - * @property {Object} nameFromCode - * @property {String} nameFromCode.8 "BACKSPACE" - */ - nameFromCode: func.invertObject(keyMap), - code: keyMap - }; - })(); + '191': 'SLASH', + '219': 'LEFTBRACKET', + '220': 'BACKSLASH', + '221': 'RIGHTBRACKET' + } + }; return key; }); diff --git a/addons/web_editor/static/lib/summernote/src/js/core/list.js b/addons/web_editor/static/lib/summernote/src/js/core/list.js index 2d6708838df..cc78781ea7f 100644 --- a/addons/web_editor/static/lib/summernote/src/js/core/list.js +++ b/addons/web_editor/static/lib/summernote/src/js/core/list.js @@ -1,11 +1,6 @@ define(['summernote/core/func'], function (func) { /** - * @class core.list - * * list utils - * - * @singleton - * @alternateClassName list */ var list = (function () { /** @@ -69,17 +64,10 @@ define(['summernote/core/func'], function (func) { }; /** - * returns index of item - */ - var indexOf = function (array, item) { - return $.inArray(item, array); - }; - - /** * returns true if the value is present in the list. */ var contains = function (array, item) { - return indexOf(array, item) !== -1; + return $.inArray(item, array) !== -1; }; /** @@ -164,7 +152,7 @@ define(['summernote/core/func'], function (func) { * @param {Array} array */ var next = function (array, item) { - var idx = indexOf(array, item); + var idx = array.indexOf(item); if (idx === -1) { return null; } return array[idx + 1]; @@ -175,11 +163,12 @@ define(['summernote/core/func'], function (func) { * @param {Array} array */ var prev = function (array, item) { - var idx = indexOf(array, item); + var idx = array.indexOf(item); if (idx === -1) { return null; } return array[idx - 1]; }; + return { head: head, last: last, initial: initial, tail: tail, prev: prev, next: next, find: find, contains: contains, diff --git a/addons/web_editor/static/lib/summernote/src/js/core/range.js b/addons/web_editor/static/lib/summernote/src/js/core/range.js index cc8d0683d14..9611e09c72b 100644 --- a/addons/web_editor/static/lib/summernote/src/js/core/range.js +++ b/addons/web_editor/static/lib/summernote/src/js/core/range.js @@ -5,6 +5,13 @@ define([ 'summernote/core/dom' ], function (agent, func, list, dom) { + /** + * Data structure + * - {BoundaryPoint}: a point of dom tree + * - {BoundaryPoints}: two boundaryPoints corresponding to the start and the end of the Range + * + * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Position + */ var range = (function () { /** @@ -111,7 +118,6 @@ define([ /** * Wrapped Range * - * @constructor * @param {Node} sc - start container * @param {Number} so - start offset * @param {Node} ec - end container @@ -183,48 +189,25 @@ define([ } else { nativeRng.select(); } - - return this; }; /** * @return {WrappedRange} */ this.normalize = function () { - - /** - * @param {BoundaryPoint} point - * @param {Boolean} isLeftToRight - * @return {BoundaryPoint} - */ - var getVisiblePoint = function (point, isLeftToRight) { - if ((dom.isVisiblePoint(point) && !dom.isEdgePoint(point)) || - (dom.isVisiblePoint(point) && dom.isRightEdgePoint(point) && !isLeftToRight) || - (dom.isVisiblePoint(point) && dom.isLeftEdgePoint(point) && isLeftToRight) || - (dom.isVisiblePoint(point) && dom.isBlock(point.node) && dom.isEmpty(point.node))) { - return point; - } - - // point on block's edge - var block = dom.ancestor(point.node, dom.isBlock); - if (((dom.isLeftEdgePointOf(point, block) || dom.isVoid(dom.prevPoint(point).node)) && !isLeftToRight) || - ((dom.isRightEdgePointOf(point, block) || dom.isVoid(dom.nextPoint(point).node)) && isLeftToRight)) { - - // returns point already on visible point - if (dom.isVisiblePoint(point)) { - return point; + var getVisiblePoint = function (point) { + if (!dom.isVisiblePoint(point)) { + if (dom.isLeftEdgePoint(point)) { + point = dom.nextPointUntil(point, dom.isVisiblePoint); + } else if (dom.isRightEdgePoint(point)) { + point = dom.prevPointUntil(point, dom.isVisiblePoint); } - // reverse direction - isLeftToRight = !isLeftToRight; } - - var nextPoint = isLeftToRight ? dom.nextPointUntil(dom.nextPoint(point), dom.isVisiblePoint) : - dom.prevPointUntil(dom.prevPoint(point), dom.isVisiblePoint); - return nextPoint || point; + return point; }; - var endPoint = getVisiblePoint(this.getEndPoint(), false); - var startPoint = this.isCollapsed() ? endPoint : getVisiblePoint(this.getStartPoint(), true); + var startPoint = getVisiblePoint(this.getStartPoint()); + var endPoint = getVisiblePoint(this.getStartPoint()); return new WrappedRange( startPoint.node, @@ -370,44 +353,42 @@ define([ * delete contents on range * @return {WrappedRange} */ - if(_.isUndefined(this.deleteContents)) // ODOO: ability to override by prototype - this.deleteContents = function () { - if (this.isCollapsed()) { - return this; - } - - var rng = this.splitText(); - var nodes = rng.nodes(null, { - fullyContains: true - }); - - // find new cursor point - var point = dom.prevPointUntil(rng.getStartPoint(), function (point) { - return !list.contains(nodes, point.node); - }); - - var emptyParents = []; - $.each(nodes, function (idx, node) { - // find empty parents - var parent = node.parentNode; - if (point.node !== parent && dom.nodeLength(parent) === 1) { - emptyParents.push(parent); - } - dom.remove(node, false); - }); - - // remove empty parents - $.each(emptyParents, function (idx, node) { - dom.remove(node, false); - }); - - return new WrappedRange( - point.node, - point.offset, - point.node, - point.offset - ).normalize(); - }; + // this.deleteContents = function () { + // if (this.isCollapsed()) { + // return this; + // } + + // var rng = this.splitText(); + // var nodes = rng.nodes(null, { + // fullyContains: true + // }); + + // var point = dom.prevPointUntil(rng.getStartPoint(), function (point) { + // return !list.contains(nodes, point.node); + // }); + + // var emptyParents = []; + // $.each(nodes, function (idx, node) { + // // find empty parents + // var parent = node.parentNode; + // if (point.node !== parent && dom.nodeLength(parent) === 1) { + // emptyParents.push(parent); + // } + // dom.remove(node, false); + // }); + + // // remove empty parents + // $.each(emptyParents, function (idx, node) { + // dom.remove(node, false); + // }); + + // return new WrappedRange( + // point.node, + // point.offset, + // point.node, + // point.offset + // ); + // }; /** * makeIsOn: return isOn(pred) function @@ -427,6 +408,36 @@ define([ this.isOnAnchor = makeIsOn(dom.isAnchor); // isOnAnchor: judge whether range is on cell node or not this.isOnCell = makeIsOn(dom.isCell); + // isOnAnchor: judge whether range is an image node or not + this.isOnImg = function () { + var nb = 0; + var image; + var startPoint = {node: sc.childNodes.length && sc.childNodes[so] || sc}; + startPoint.offset = startPoint.node === sc ? so : 0; + var endPoint = {node: ec.childNodes.length && ec.childNodes[eo] || ec}; + endPoint.offset = endPoint.node === ec ? eo : 0; + + if (dom.isImg(startPoint.node)) { + nb ++; + image = startPoint.node; + } + dom.walkPoint(startPoint, endPoint, function (point) { + if (!dom.isText(endPoint.node) && point.node === endPoint.node && point.offset === endPoint.offset) { + return; + } + var node = point.node.childNodes.length && point.node.childNodes[point.offset] || point.node; + var offset = node === point.node ? point.offset : 0; + var isImg = dom.ancestor(node, dom.isImg); + if (!isImg && ((!dom.isBR(node) && !dom.isText(node)) || (offset && node.textContent.length !== offset && node.textContent.match(/\S|\u00A0/)))) { + nb++; + } + if (isImg && image !== isImg) { + image = isImg; + nb ++; + } + }); + return nb === 1 && image; + }; /** * @param {Function} pred @@ -456,20 +467,13 @@ define([ this.wrapBodyInlineWithPara = function () { if (dom.isBodyContainer(sc) && dom.isEmpty(sc)) { sc.innerHTML = dom.emptyPara; - return new WrappedRange(sc.firstChild, 0, sc.firstChild, 0); + return new WrappedRange(sc.firstChild, 0); } - /** - * [workaround] firefox often create range on not visible point. so normalize here. - * - firefox: |<p>text</p>| - * - chrome: <p>|text|</p> - */ - var rng = this.normalize(); if (dom.isParaInline(sc) || dom.isPara(sc)) { - return rng; + return this.normalize(); } - // ODOO: insert a p tag when try to insert a br with insertNode method, if the editor is inside a p, li, h1... (start_modification // if apply the editor to a P, LI... or inside a P, LI... if (dom.isText(sc)) { var node = sc; @@ -480,18 +484,17 @@ define([ } } } - // ODOO: end_modification) // find inline top ancestor var topAncestor; - if (dom.isInline(rng.sc)) { - var ancestors = dom.listAncestor(rng.sc, func.not(dom.isInline)); + if (dom.isInline(sc)) { + var ancestors = dom.listAncestor(sc, func.not(dom.isInline)); topAncestor = list.last(ancestors); if (!dom.isInline(topAncestor)) { - topAncestor = ancestors[ancestors.length - 2] || rng.sc.childNodes[rng.so]; + topAncestor = ancestors[ancestors.length - 2] || sc.childNodes[so]; } } else { - topAncestor = rng.sc.childNodes[rng.so > 0 ? rng.so - 1 : 0]; + topAncestor = sc.childNodes[so - 1]; } // siblings not in paragraph @@ -511,79 +514,52 @@ define([ * insert node at current cursor * * @param {Node} node + * @param {Boolean} [isInline] * @return {Node} */ - this.insertNode = function (node) { - var rng = this.wrapBodyInlineWithPara().deleteContents(); - var info = dom.splitPoint(rng.getStartPoint(), dom.isInline(node)); + this.insertNode = function (node, isInline) { + var rng = this.wrapBodyInlineWithPara(); + var point = rng.getStartPoint(); + + var splitRoot, container, pivot; + if (isInline) { + container = dom.isPara(point.node) ? point.node : point.node.parentNode; + if (dom.isPara(point.node)) { + pivot = point.node.childNodes[point.offset]; + } else { + pivot = dom.splitTree(point.node, point); + } + } else { + // splitRoot will be childNode of container + var ancestors = dom.listAncestor(point.node, dom.isBodyContainer); + var topAncestor = list.last(ancestors) || point.node; + + if (dom.isBodyContainer(topAncestor)) { + splitRoot = ancestors[ancestors.length - 2]; + container = topAncestor; + } else { + splitRoot = topAncestor; + container = splitRoot.parentNode; + } + pivot = splitRoot && dom.splitTree(splitRoot, point); + } - if (info.rightNode) { - info.rightNode.parentNode.insertBefore(node, info.rightNode); + if (pivot) { + pivot.parentNode.insertBefore(node, pivot); } else { - info.container.appendChild(node); + container.appendChild(node); } return node; }; - - /** - * insert html at current cursor - */ - this.pasteHTML = function (markup) { - var contentsContainer = $('<div></div>').html(markup)[0]; - var childNodes = list.from(contentsContainer.childNodes); - - var rng = this.wrapBodyInlineWithPara().deleteContents(); - - return childNodes.reverse().map(function (childNode) { - return rng.insertNode(childNode); - }).reverse(); - }; - /** - * returns text in range - * - * @return {String} - */ this.toString = function () { var nativeRng = nativeRange(); return agent.isW3CRangeSupport ? nativeRng.toString() : nativeRng.text; }; - - /** - * returns range for word before cursor - * - * @param {Boolean} [findAfter] - find after cursor, default: false - * @return {WrappedRange} - */ - this.getWordRange = function (findAfter) { - var endPoint = this.getEndPoint(); - - if (!dom.isCharPoint(endPoint)) { - return this; - } - - var startPoint = dom.prevPointUntil(endPoint, function (point) { - return !dom.isCharPoint(point); - }); - - if (findAfter) { - endPoint = dom.nextPointUntil(endPoint, function (point) { - return !dom.isCharPoint(point); - }); - } - - return new WrappedRange( - startPoint.node, - startPoint.offset, - endPoint.node, - endPoint.offset - ); - }; /** * create offsetPath bookmark - * * @param {Node} editable */ this.bookmark = function (editable) { @@ -600,24 +576,6 @@ define([ }; /** - * create offsetPath bookmark base on paragraph - * - * @param {Node[]} paras - */ - this.paraBookmark = function (paras) { - return { - s: { - path: list.tail(dom.makeOffsetPath(list.head(paras), sc)), - offset: so - }, - e: { - path: list.tail(dom.makeOffsetPath(list.last(paras), ec)), - offset: eo - } - }; - }; - - /** * getClientRects * @return {Rect[]} */ @@ -626,43 +584,30 @@ define([ return nativeRng.getClientRects(); }; }; - - /** - * @class core.range - * - * Data structure - * * BoundaryPoint: a point of dom tree - * * BoundaryPoints: two boundaryPoints corresponding to the start and the end of the Range - * - * See to http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Position - * - * @singleton - * @alternateClassName range - */ + return { - WrappedRange: WrappedRange, // ODOO: give access to WrappedRange + + WrappedRange: WrappedRange, // odoo change for overwrite + /** - * @method - * * create Range Object From arguments or Browser Selection * * @param {Node} sc - start container * @param {Number} so - start offset * @param {Node} ec - end container * @param {Number} eo - end offset - * @return {WrappedRange} */ create : function (sc, so, ec, eo) { if (!arguments.length) { // from Browser Selection if (agent.isW3CRangeSupport) { var selection = document.getSelection(); - if (!selection || selection.rangeCount === 0) { + if (selection.rangeCount === 0) { return null; } else if (dom.isBody(selection.anchorNode)) { // Firefox: returns entire body as range on initialization. We won't never need it. return null; } - + var nativeRng = selection.getRangeAt(0); sc = nativeRng.startContainer; so = nativeRng.startOffset; @@ -690,6 +635,7 @@ define([ ec = endPoint.cont; eo = endPoint.offset; } + } else if (arguments.length === 2) { //collapsed ec = sc; eo = so; @@ -698,8 +644,6 @@ define([ }, /** - * @method - * * create WrappedRange from node * * @param {Node} node @@ -713,13 +657,13 @@ define([ // browsers can't target a picture or void node if (dom.isVoid(sc)) { - so = dom.listPrev(sc).length - 1; + so = dom.listPrev(sc).length-1; sc = sc.parentNode; } if (dom.isBR(ec)) { - eo = dom.listPrev(ec).length - 1; + eo = dom.listPrev(ec).length-1; ec = ec.parentNode; - } else if (dom.isVoid(ec)) { + } else if (dom.isVoid(ec) || dom.isImg(sc)) { eo = dom.listPrev(ec).length; ec = ec.parentNode; } @@ -728,32 +672,10 @@ define([ }, /** - * create WrappedRange from node after position - * - * @param {Node} node - * @return {WrappedRange} - */ - createFromNodeBefore: function (node) { - return this.createFromNode(node).collapse(true); - }, - - /** - * create WrappedRange from node after position - * - * @param {Node} node - * @return {WrappedRange} - */ - createFromNodeAfter: function (node) { - return this.createFromNode(node).collapse(); - }, - - /** - * @method - * - * create WrappedRange from bookmark + * create WrappedRange from Bookmark * * @param {Node} editable - * @param {Object} bookmark + * @param {Obkect} bookmark * @return {WrappedRange} */ createFromBookmark : function (editable, bookmark) { @@ -762,24 +684,6 @@ define([ var ec = dom.fromOffsetPath(editable, bookmark.e.path); var eo = bookmark.e.offset; return new WrappedRange(sc, so, ec, eo); - }, - - /** - * @method - * - * create WrappedRange from paraBookmark - * - * @param {Object} bookmark - * @param {Node[]} paras - * @return {WrappedRange} - */ - createFromParaBookmark: function (bookmark, paras) { - var so = bookmark.s.offset; - var eo = bookmark.e.offset; - var sc = dom.fromOffsetPath(list.head(paras), bookmark.s.path); - var ec = dom.fromOffsetPath(list.last(paras), bookmark.e.path); - - return new WrappedRange(sc, so, ec, eo); } }; })(); diff --git a/addons/web_editor/static/lib/summernote/src/js/editing/Bullet.js b/addons/web_editor/static/lib/summernote/src/js/editing/Bullet.js index 6a115edaa90..d0b0f2e9169 100644 --- a/addons/web_editor/static/lib/summernote/src/js/editing/Bullet.js +++ b/addons/web_editor/static/lib/summernote/src/js/editing/Bullet.js @@ -5,17 +5,9 @@ define([ 'summernote/core/range' ], function (list, func, dom, range) { - /** - * @class editing.Bullet - * - * @alternateClassName Bullet - */ var Bullet = function () { /** - * @method insertOrderedList - * * toggle ordered list - * * @type command */ this.insertOrderedList = function () { @@ -23,10 +15,7 @@ define([ }; /** - * @method insertUnorderedList - * * toggle unordered list - * * @type command */ this.insertUnorderedList = function () { @@ -34,10 +23,7 @@ define([ }; /** - * @method indent - * * indent - * * @type command */ this.indent = function () { @@ -64,10 +50,7 @@ define([ }; /** - * @method outdent - * * outdent - * * @type command */ this.outdent = function () { @@ -95,10 +78,7 @@ define([ }; /** - * @method toggleList - * * toggle list - * * @param {String} listName - OL or UL */ this.toggleList = function (listName) { @@ -106,16 +86,13 @@ define([ var rng = range.create().wrapBodyInlineWithPara(); var paras = rng.nodes(dom.isPara, { includeAncestor: true }); - var bookmark = rng.paraBookmark(paras); var clustereds = list.clusterBy(paras, func.peq2('parentNode')); // paragraph to list if (list.find(paras, dom.isPurePara)) { - var wrappedParas = []; $.each(clustereds, function (idx, paras) { - wrappedParas = wrappedParas.concat(self.wrapList(paras, listName)); + self.wrapList(paras, listName); }); - paras = wrappedParas; // list to paragraph or change list style } else { var diffLists = rng.nodes(dom.isList, { @@ -129,19 +106,16 @@ define([ dom.replace(listNode, listName); }); } else { - paras = this.releaseList(clustereds, true); + this.releaseList(clustereds, true); } } - range.createFromParaBookmark(bookmark, paras).select(); + rng.select(); }; /** - * @method wrapList - * * @param {Node[]} paras * @param {String} listName - * @return {Node[]} */ this.wrapList = function (paras, listName) { var head = list.head(paras); @@ -153,7 +127,7 @@ define([ var listNode = prevList || dom.insertAfter(dom.create(listName || 'UL'), last); // P to LI - paras = paras.map(function (para) { + paras = $.map(paras, function (para) { return dom.isPurePara(para) ? dom.replace(para, 'LI') : para; }); @@ -164,13 +138,9 @@ define([ dom.appendChildNodes(listNode, list.from(nextList.childNodes)); dom.remove(nextList); } - - return paras; }; /** - * @method releaseList - * * @param {Array[]} clustereds * @param {Boolean} isEscapseToBody * @return {Node[]} @@ -187,23 +157,19 @@ define([ var lastList = headList.childNodes.length > 1 ? dom.splitTree(headList, { node: last.parentNode, offset: dom.position(last) + 1 - }, { - isSkipPaddingBlankHTML: true - }) : null; + }, true) : null; var middleList = dom.splitTree(headList, { node: head.parentNode, offset: dom.position(head) - }, { - isSkipPaddingBlankHTML: true - }); + }, true); paras = isEscapseToBody ? dom.listDescendant(middleList, dom.isLi) : list.from(middleList.childNodes).filter(dom.isLi); // LI to P if (isEscapseToBody || !dom.isList(headList.parentNode)) { - paras = paras.map(function (para) { + paras = $.map(paras, function (para) { return dom.replace(para, 'P'); }); } diff --git a/addons/web_editor/static/lib/summernote/src/js/editing/Editor.js b/addons/web_editor/static/lib/summernote/src/js/editing/Editor.js new file mode 100644 index 00000000000..1acd957096d --- /dev/null +++ b/addons/web_editor/static/lib/summernote/src/js/editing/Editor.js @@ -0,0 +1,495 @@ +define([ + 'summernote/core/agent', + 'summernote/core/func', + 'summernote/core/list', + 'summernote/core/dom', + 'summernote/core/range', + 'summernote/core/async', + 'summernote/editing/Style', + 'summernote/editing/Typing', + 'summernote/editing/Table', + 'summernote/editing/Bullet' +], function (agent, func, list, dom, range, async, + Style, Typing, Table, Bullet) { + /** + * Editor + * @class + */ + var Editor = function () { + + var style = new Style(); + var table = new Table(); + var typing = new Typing(); + var bullet = new Bullet(); + + this.style = style; // odoo change for overwrite + this.table = table; // odoo change for overwrite + this.typing = typing; // odoo change for overwrite + this.bullet = bullet; // odoo change for overwrite + + /** + * create range + */ + this.createRange = function ($editable) { + $editable.focus(); + return range.create(); + }; + + /** + * save current range + * + * @param {jQuery} $editable + */ + this.saveRange = function ($editable, thenCollapse) { + var r = range.create(); + if (!r || ($editable[0] !== r.sc && !$.contains($editable[0], r.sc))) { + $editable.focus(); + } + $editable.data('range', range.create()); + if (thenCollapse) { + range.create().collapse().select(); + } + }; + + this.saveNode = function ($editable) { + // copy child node reference + var copy = []; + for (var key = 0, len = $editable[0].childNodes.length; key < len; key++) { + copy.push($editable[0].childNodes[key]); + } + $editable.data('childNodes', copy); + }; + + /** + * restore lately range + * + * @param {jQuery} $editable + */ + this.restoreRange = function ($editable) { + var rng = $editable.data('range'); + if (rng) { + rng.select(); + $editable.focus(); + } + }; + + this.restoreNode = function ($editable) { + $editable.html(''); + var child = $editable.data('childNodes'); + for (var index = 0, len = child.length; index < len; index++) { + $editable[0].appendChild(child[index]); + } + }; + /** + * current style + */ + this.currentStyle = function () { + var rng = range.create(); + return rng ? rng.isOnEditable() && style.current(rng) : false; + }; + + var triggerOnChange = this.triggerOnChange = function ($editable) { + var onChange = $editable.data('callbacks').onChange; + if (onChange) { + onChange($editable.html(), $editable); + } + }; + + /** + * undo + * @param {jQuery} $editable + */ + this.undo = function ($editable) { + $editable.data('NoteHistory').undo(); + triggerOnChange($editable); + }; + + /** + * redo + * @param {jQuery} $editable + */ + this.redo = function ($editable) { + $editable.data('NoteHistory').redo(); + triggerOnChange($editable); + }; + + /** + * after command + * @param {jQuery} $editable + */ + var afterCommand = this.afterCommand = function ($editable) { + $editable.data('NoteHistory').recordUndo(); + triggerOnChange($editable); + }; + + /* jshint ignore:start */ + // native commands(with execCommand), generate function for execCommand + var commands = ['bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript', + 'justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull', + 'formatBlock', 'removeFormat', + 'backColor', 'foreColor', 'insertHorizontalRule', 'fontName']; + + for (var idx = 0, len = commands.length; idx < len; idx ++) { + this[commands[idx]] = (function (sCmd) { + return function ($editable, value) { + document.execCommand(sCmd, false, value); + + afterCommand($editable); + }; + })(commands[idx]); + } + /* jshint ignore:end */ + + /** + * handle tab key + * + * @param {jQuery} $editable + * @param {Object} options + */ + this.tab = function ($editable, options) { + var rng = range.create(); + if (rng.isCollapsed() && rng.isOnCell()) { + table.tab(rng); + } else { + typing.insertTab($editable, rng, options.tabsize); + afterCommand($editable); + } + }; + + /** + * handle shift+tab key + */ + this.untab = function () { + var rng = range.create(); + if (rng.isCollapsed() && rng.isOnCell()) { + table.tab(rng, true); + } + }; + + /** + * insert paragraph + * + * @param {Node} $editable + */ + this.insertParagraph = function ($editable) { + typing.insertParagraph($editable); + afterCommand($editable); + }; + + /** + * @param {jQuery} $editable + */ + this.insertOrderedList = function ($editable) { + bullet.insertOrderedList($editable); + afterCommand($editable); + }; + + /** + * @param {jQuery} $editable + */ + this.insertUnorderedList = function ($editable) { + bullet.insertUnorderedList($editable); + afterCommand($editable); + }; + + /** + * @param {jQuery} $editable + */ + this.indent = function ($editable) { + bullet.indent($editable); + afterCommand($editable); + }; + + /** + * @param {jQuery} $editable + */ + this.outdent = function ($editable) { + bullet.outdent($editable); + afterCommand($editable); + }; + + /** + * insert image + * + * @param {jQuery} $editable + * @param {String} sUrl + */ + this.insertImage = function ($editable, sUrl, filename) { + async.createImage(sUrl, filename).then(function ($image) { + $image.css({ + display: '', + width: Math.min($editable.width(), $image.width()) + }); + range.create().insertNode($image[0]); + afterCommand($editable); + }).fail(function () { + var callbacks = $editable.data('callbacks'); + if (callbacks.onImageUploadError) { + callbacks.onImageUploadError(); + } + }); + }; + + /** + * insert node + * @param {Node} $editable + * @param {Node} node + * @param {Boolean} [isInline] + */ + this.insertNode = function ($editable, node, isInline) { + range.create().insertNode(node, isInline); + afterCommand($editable); + }; + + /** + * insert text + * @param {Node} $editable + * @param {String} text + */ + this.insertText = function ($editable, text) { + var textNode = this.createRange($editable).insertNode(dom.createText(text), true); + range.create(textNode, dom.nodeLength(textNode)).select(); + afterCommand($editable); + }; + + /** + * formatBlock + * + * @param {jQuery} $editable + * @param {String} tagName + */ + this.formatBlock = function ($editable, tagName) { + tagName = agent.isMSIE ? '<' + tagName + '>' : tagName; + document.execCommand('FormatBlock', false, tagName); + afterCommand($editable); + }; + + this.formatPara = function ($editable) { + this.formatBlock($editable, 'P'); + afterCommand($editable); + }; + + /* jshint ignore:start */ + for (var idx = 1; idx <= 6; idx ++) { + this['formatH' + idx] = function (idx) { + return function ($editable) { + this.formatBlock($editable, 'H' + idx); + }; + }(idx); + }; + /* jshint ignore:end */ + + /** + * fontsize + * FIXME: Still buggy + * + * @param {jQuery} $editable + * @param {String} value - px + */ + this.fontSize = function ($editable, value) { + document.execCommand('fontSize', false, 3); + if (agent.isFF) { + // firefox: <font size="3"> to <span style='font-size={value}px;'>, buggy + $editable.find('font[size=3]').removeAttr('size').css('font-size', value + 'px'); + } else { + // chrome: <span style="font-size: medium"> to <span style='font-size={value}px;'> + $editable.find('span').filter(function () { + return this.style.fontSize === 'medium'; + }).css('font-size', value + 'px'); + } + + afterCommand($editable); + }; + + /** + * lineHeight + * @param {jQuery} $editable + * @param {String} value + */ + this.lineHeight = function ($editable, value) { + style.stylePara(range.create(), { + lineHeight: value + }); + afterCommand($editable); + }; + + /** + * unlink + * + * @type command + * + * @param {jQuery} $editable + */ + this.unlink = function ($editable) { + var rng = range.create(); + if (rng.isOnAnchor()) { + var anchor = dom.ancestor(rng.sc, dom.isAnchor); + rng = range.createFromNode(anchor); + rng.select(); + document.execCommand('unlink'); + + afterCommand($editable); + } + }; + + /** + * create link + * + * @type command + * + * @param {jQuery} $editable + * @param {Object} linkInfo + * @param {Object} options + */ + this.createLink = function ($editable, linkInfo, options) { + var linkUrl = linkInfo.url; + var linkText = linkInfo.text; + var isNewWindow = linkInfo.newWindow; + var className = linkInfo.className || null; + var rng = linkInfo.range; + + if (options.onCreateLink) { + linkUrl = options.onCreateLink(linkUrl); + } + + rng = rng.deleteContents(true); + var anchor = dom.ancestor(rng.sc, dom.isAnchor); + + // Create a new link when there is no anchor on range. + if (!anchor) { + anchor = rng.insertNode($('<A>' + linkText + '</A>')[0], true); + } + $(anchor).html(linkText).attr({ + 'href': linkUrl, + 'target': isNewWindow ? '_blank' : null, + 'class': className + }); + + range.createFromNode(anchor).select(); + afterCommand($editable); + }; + + /** + * returns link info + * + * @return {Object} + */ + this.getLinkInfo = function ($editable) { + $editable.focus(); + + var rng = range.create().expand(dom.isAnchor); + + // Get the first anchor on range(for edit). + var $anchor = $(list.head(rng.nodes(dom.isAnchor))); + + return { + range: rng, + text: rng.toString(), + isNewWindow: $anchor.length ? $anchor.attr('target') === '_blank' : true, + url: $anchor.length ? $anchor.attr('href') : '' + }; + }; + + this.color = function ($editable, sObjColor) { + var oColor = JSON.parse(sObjColor); + var foreColor = oColor.foreColor, backColor = oColor.backColor; + + if (foreColor) { document.execCommand('foreColor', false, foreColor); } + if (backColor) { document.execCommand('backColor', false, backColor); } + + afterCommand($editable); + }; + + this.insertTable = function ($editable, sDim) { + var dimension = sDim.split('x'); + var rng = range.create(); + rng = rng.deleteContents(); + rng.insertNode(table.createTable(dimension[0], dimension[1])); + afterCommand($editable); + }; + + /** + * @return {Node} image from the current range selection + */ + function getImgTarget () { + var rng = range.create(); + var target = rng.sc.childNodes.length && rng.sc.childNodes[rng.so] || rng.sc; + return target; + } + + /** + * @param {jQuery} $editable + * @param {String} value + */ + this.floatMe = function ($editable, value) { + var $target = $( getImgTarget() ); + $target.css('float', value); + afterCommand($editable); + }; + + this.imageShape = function ($editable, value) { + var $target = $( getImgTarget() ); + $target.removeClass('img-rounded img-circle img-thumbnail'); + + if (value) { + $target.addClass(value); + } + + afterCommand($editable); + }; + + /** + * resize overlay element + * @param {jQuery} $editable + * @param {String} value + */ + this.resize = function ($editable, value) { + var $target = $( getImgTarget() ); + $target.css({ + width: value * 100 + '%', + height: '' + }); + + afterCommand($editable); + }; + + /** + * @param {Position} pos + * @param {jQuery} $target - target element + * @param {Boolean} [bKeepRatio] - keep ratio + */ + this.resizeTo = function (pos, $target, bKeepRatio) { + var imageSize; + if (bKeepRatio) { + var newRatio = pos.y / pos.x; + var ratio = $target.data('ratio'); + imageSize = { + width: ratio > newRatio ? pos.x : pos.y / ratio, + height: ratio > newRatio ? pos.x * ratio : pos.y + }; + } else { + imageSize = { + width: pos.x, + height: pos.y + }; + } + + $target.css(imageSize); + }; + + /** + * remove media object + * + * @param {jQuery} $editable + * @param {String} value - dummy argument (for keep interface) + */ + this.removeMedia = function ($editable, value) { + var $target = $( getImgTarget() ); + $target.detach(); + + afterCommand($editable); + }; + }; + + return Editor; +}); diff --git a/addons/web_editor/static/lib/summernote/src/js/editing/History.js b/addons/web_editor/static/lib/summernote/src/js/editing/History.js index f50e87fe166..dc301bf957b 100644 --- a/addons/web_editor/static/lib/summernote/src/js/editing/History.js +++ b/addons/web_editor/static/lib/summernote/src/js/editing/History.js @@ -1,12 +1,10 @@ -define(['summernote/core/range'], function (range) { +define(['summernote/core/range', 'summernote/core/dom'], function (range, dom) { /** - * @class editing.History - * - * Editor History - * + * History + * @class */ var History = function ($editable) { - var stack = [], stackOffset = -1; + var stack = [], stackOffset = -1, hasUndo = false; var editable = $editable[0]; var makeSnapshot = function () { @@ -15,7 +13,7 @@ define(['summernote/core/range'], function (range) { return { contents: $editable.html(), - bookmark: (rng ? rng.bookmark(editable) : emptyBookmark) + bookmark: (rng && dom.ancestor(rng.sc, dom.isEditable) ? rng.bookmark(editable) : emptyBookmark) }; }; @@ -28,29 +26,21 @@ define(['summernote/core/range'], function (range) { } }; - /** - * undo - */ this.undo = function () { - // Create snap shot if not yet recorded - if ($editable.html() !== stack[stackOffset].contents) { - this.recordUndo(); - } - - if (0 < stackOffset) { + if (0 < stackOffset || (stack.length - 1 === stackOffset && hasUndo)) { + if (stack.length - 1 === stackOffset) { + this.recordUndo(); + } stackOffset--; applySnapshot(stack[stackOffset]); + hasUndo = !!stackOffset; } }; - /* ODOO: to suggest upstream */ this.hasUndo = function () { - return 0 < stackOffset; + return 0 < stackOffset || hasUndo; }; - /** - * redo - */ this.redo = function () { if (stack.length - 1 > stackOffset) { stackOffset++; @@ -58,24 +48,49 @@ define(['summernote/core/range'], function (range) { } }; - /* ODOO: to suggest upstream */ this.hasRedo = function () { return stack.length - 1 > stackOffset; }; - /** - * recorded undo - */ + var last; this.recordUndo = function () { - stackOffset++; + // test event for firefox: remove stack of history because event doesn't exists + var key = typeof event !== 'undefined' ? event : false; + if (key && !event.metaKey && !event.ctrlKey && !event.altKey && event.type === "keydown") { + key = event.type + "-"; + if (event.which === 8 || event.which === 46) key += 'delete'; + else if (event.which === 13) key += 'enter'; + else key += 'other'; + if (key === last) return; + hasUndo = true; + } + last = key; // Wash out stack after stackOffset - if (stack.length > stackOffset) { - stack = stack.slice(0, stackOffset); + if (stack.length > stackOffset+1) { + stack = stack.slice(0, stackOffset+1); + } + + if (stack[stackOffset] && stack[stackOffset].contents === $editable.html()) { + return; } + stackOffset++; + // Create new snapshot and push it to the end stack.push(makeSnapshot()); + return true; + }; + + this.splitNext = function () { + last = false; + }; + + this.reset = function () { + last = false; + stack = []; + stackOffset = -1; + this.recordUndo(); }; // Create first undo stack diff --git a/addons/web_editor/static/lib/summernote/src/js/editing/Style.js b/addons/web_editor/static/lib/summernote/src/js/editing/Style.js index bf749cd817b..835d575be1b 100644 --- a/addons/web_editor/static/lib/summernote/src/js/editing/Style.js +++ b/addons/web_editor/static/lib/summernote/src/js/editing/Style.js @@ -1,28 +1,20 @@ define([ 'summernote/core/agent', - 'summernote/core/func', - 'summernote/core/list', 'summernote/core/dom' -], function (agent, func, list, dom) { +], function (agent, dom) { /** - * @class editing.Style - * * Style - * + * @class */ var Style = function () { /** - * @method jQueryCSS - * - * [workaround] for old jQuery * passing an array of style properties to .css() * will result in an object of property-value pairs. * (compability with version < 1.9) * - * @private * @param {jQuery} $obj * @param {Array} propertyNames - An array of one or more CSS properties. - * @return {Object} + * @returns {Object} */ var jQueryCSS = function ($obj, propertyNames) { if (agent.jqueryVersion < 1.9) { @@ -36,19 +28,6 @@ define([ }; /** - * returns style object from node - * - * @param {jQuery} $node - * @return {Object} - */ - this.fromNode = function ($node) { - var properties = ['font-family', 'font-size', 'text-align', 'list-style-type', 'line-height']; - var styleInfo = jQueryCSS($node, properties) || {}; - styleInfo['font-size'] = parseInt(styleInfo['font-size'], 10); - return styleInfo; - }; - - /** * paragraph level style * * @param {WrappedRange} rng @@ -63,58 +42,6 @@ define([ }; /** - * insert and returns styleNodes on range. - * - * @param {WrappedRange} rng - * @param {Object} [options] - options for styleNodes - * @param {String} [options.nodeName] - default: `SPAN` - * @param {Boolean} [options.expandClosestSibling] - default: `false` - * @param {Boolean} [options.onlyPartialContains] - default: `false` - * @return {Node[]} - */ - this.styleNodes = function (rng, options) { - rng = rng.splitText(); - - var nodeName = options && options.nodeName || 'SPAN'; - var expandClosestSibling = !!(options && options.expandClosestSibling); - var onlyPartialContains = !!(options && options.onlyPartialContains); - - if (rng.isCollapsed()) { - return [rng.insertNode(dom.create(nodeName))]; - } - - var pred = dom.makePredByNodeName(nodeName); - var nodes = rng.nodes(dom.isText, { - fullyContains: true - }).map(function (text) { - return dom.singleChildAncestor(text, pred) || dom.wrap(text, nodeName); - }); - - if (expandClosestSibling) { - if (onlyPartialContains) { - var nodesInRange = rng.nodes(); - // compose with partial contains predication - pred = func.and(pred, function (node) { - return list.contains(nodesInRange, node); - }); - } - - return nodes.map(function (node) { - var siblings = dom.withClosestSiblings(node, pred); - var head = list.head(siblings); - var tails = list.tail(siblings); - $.each(tails, function (idx, elem) { - dom.appendChildNodes(head, elem.childNodes); - dom.remove(elem); - }); - return list.head(siblings); - }); - } else { - return nodes; - } - }; - - /** * get current style on cursor * * @param {WrappedRange} rng @@ -122,7 +49,10 @@ define([ */ this.current = function (rng) { var $cont = $(dom.isText(rng.sc) ? rng.sc.parentNode : rng.sc); - var styleInfo = this.fromNode($cont); + var properties = ['font-family', 'font-size', 'text-align', 'list-style-type', 'line-height']; + var styleInfo = jQueryCSS($cont, properties) || {}; + + styleInfo['font-size'] = parseInt(styleInfo['font-size'], 10); // document.queryCommandState for toggle state styleInfo['font-bold'] = document.queryCommandState('bold') ? 'bold' : 'normal'; @@ -149,6 +79,7 @@ define([ styleInfo['line-height'] = lineHeight.toFixed(1); } + styleInfo.image = rng.isOnImg(); styleInfo.anchor = rng.isOnAnchor() && dom.ancestor(rng.sc, dom.isAnchor); styleInfo.ancestors = dom.listAncestor(rng.sc, dom.isEditable); styleInfo.range = rng; diff --git a/addons/web_editor/static/lib/summernote/src/js/editing/Table.js b/addons/web_editor/static/lib/summernote/src/js/editing/Table.js index 634d5a79d7e..291f85128e5 100644 --- a/addons/web_editor/static/lib/summernote/src/js/editing/Table.js +++ b/addons/web_editor/static/lib/summernote/src/js/editing/Table.js @@ -2,10 +2,8 @@ define([ 'summernote/core/dom', 'summernote/core/range', 'summernote/core/list' ], function (dom, range, list) { /** - * @class editing.Table - * * Table - * + * @class */ var Table = function () { /** diff --git a/addons/web_editor/static/lib/summernote/src/js/editing/Typing.js b/addons/web_editor/static/lib/summernote/src/js/editing/Typing.js index f95eba9e9f3..2a094cafa22 100644 --- a/addons/web_editor/static/lib/summernote/src/js/editing/Typing.js +++ b/addons/web_editor/static/lib/summernote/src/js/editing/Typing.js @@ -1,24 +1,12 @@ define([ 'summernote/core/dom', - 'summernote/core/range', - 'summernote/editing/Bullet' -], function (dom, range, Bullet) { + 'summernote/core/range' +], function (dom, range) { - /** - * @class editing.Typing - * - * Typing - * - */ var Typing = function () { - // a Bullet instance to toggle lists off - var bullet = new Bullet(); - /** - * insert tab - * - * @param {jQuery} $editable + * @param {jQuery} $editable * @param {WrappedRange} rng * @param {Number} tabsize */ @@ -49,22 +37,14 @@ define([ var nextPara; // on paragraph: split paragraph if (splitRoot) { - // if it is an empty line with li - if (dom.isEmpty(splitRoot) && dom.isLi(splitRoot)) { - // disable UL/OL and escape! - bullet.toggleList(splitRoot.parentNode.nodeName); - return; - // if new line has content (not a line break) - } else { - nextPara = dom.splitTree(splitRoot, rng.getStartPoint()); + nextPara = dom.splitTree(splitRoot, rng.getStartPoint()); - var emptyAnchors = dom.listDescendant(splitRoot, dom.isEmptyAnchor); - emptyAnchors = emptyAnchors.concat(dom.listDescendant(nextPara, dom.isEmptyAnchor)); + var emptyAnchors = dom.listDescendant(splitRoot, dom.isEmptyAnchor); + emptyAnchors = emptyAnchors.concat(dom.listDescendant(nextPara, dom.isEmptyAnchor)); - $.each(emptyAnchors, function (idx, anchor) { - dom.remove(anchor); - }); - } + $.each(emptyAnchors, function (idx, anchor) { + dom.remove(anchor); + }); // no paragraph: insert empty paragraph } else { var next = rng.sc.childNodes[rng.so]; @@ -77,7 +57,6 @@ define([ } range.create(nextPara, 0).normalize().select(); - }; }; diff --git a/addons/web_editor/static/lib/summernote/src/js/intro.js b/addons/web_editor/static/lib/summernote/src/js/intro.js index 357132eb110..dd5e9763077 100644 --- a/addons/web_editor/static/lib/summernote/src/js/intro.js +++ b/addons/web_editor/static/lib/summernote/src/js/intro.js @@ -1,9 +1,9 @@ /** * Super simple wysiwyg editor on Bootstrap v@VERSION - * http://summernote.org/ + * http://hackerwins.github.io/summernote/ * * summernote.js - * Copyright 2013-2015 Alan Hong. and other contributors + * Copyright 2013-2014 Alan Hong. and other contributors * summernote may be freely distributed under the MIT license./ * * Date: @DATE diff --git a/addons/web_editor/static/lib/summernote/src/js/module/Button.js b/addons/web_editor/static/lib/summernote/src/js/module/Button.js index b7a1614d4db..512822aeba8 100644 --- a/addons/web_editor/static/lib/summernote/src/js/module/Button.js +++ b/addons/web_editor/static/lib/summernote/src/js/module/Button.js @@ -1,10 +1,7 @@ define([ - 'summernote/core/list', - 'summernote/core/agent' -], function (list, agent) { + 'summernote/core/list' +], function (list) { /** - * @class module.Button - * * Button */ var Button = function () { @@ -31,7 +28,6 @@ define([ /** * update button state(active or not). * - * @private * @param {String} selector * @param {Function} pred */ @@ -44,30 +40,30 @@ define([ var $img = $(styleInfo.image); btnState('button[data-event="imageShape"][data-value="img-rounded"]', function () { - return $img.hasClass('img-rounded'); + return $img.hasClass("img-rounded"); }); btnState('button[data-event="imageShape"][data-value="img-circle"]', function () { - return $img.hasClass('img-circle'); + return $img.hasClass("img-circle"); }); btnState('button[data-event="imageShape"][data-value="img-thumbnail"]', function () { - return $img.hasClass('img-thumbnail'); + return $img.hasClass("img-thumbnail"); }); btnState('button[data-event="imageShape"]:not([data-value])', function () { - return !$img.is('.img-rounded, .img-circle, .img-thumbnail'); + return !$img.is(".img-rounded, .img-circle, .img-thumbnail"); }); - var imgFloat = $img.css('float'); + var imgFloat = $img.css("float"); btnState('button[data-event="floatMe"][data-value="left"]', function () { - return imgFloat === 'left'; + return imgFloat === "left"; }); btnState('button[data-event="floatMe"][data-value="right"]', function () { - return imgFloat === 'right'; + return imgFloat === "right"; }); btnState('button[data-event="floatMe"][data-value="none"]', function () { - return imgFloat !== 'left' && imgFloat !== 'right'; + return imgFloat !== "left" && imgFloat !== "right"; }); - var style = $img.attr('style'); + var style = $img.attr("style"); btnState('button[data-event="resize"][data-value="1"]', function () { return !!/(^|\s)(max-)?width\s*:\s*100%/.test(style); }); @@ -85,18 +81,10 @@ define([ if ($fontname.length) { var selectedFont = styleInfo['font-family']; if (!!selectedFont) { - - var list = selectedFont.split(','); - for (var i = 0, len = list.length; i < len; i++) { - selectedFont = list[i].replace(/[\'\"]/g, '').replace(/\s+$/, '').replace(/^\s+/, ''); - if (agent.isFontInstalled(selectedFont)) { - break; - } - } - + selectedFont = list.head(selectedFont.split(',')); + selectedFont = selectedFont.replace(/\'/g, ''); $fontname.find('.note-current-fontname').text(selectedFont); checkDropdownMenu($fontname, selectedFont); - } } @@ -152,7 +140,7 @@ define([ * * @param {Node} button * @param {String} eventName - * @param {Mixed} value + * @param {value} value */ this.updateRecentColor = function (button, eventName, value) { var $color = $(button).closest('.note-color'); 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 deleted file mode 100644 index e2d234c9dbb..00000000000 --- a/addons/web_editor/static/lib/summernote/src/js/module/Clipboard.js +++ /dev/null @@ -1,90 +0,0 @@ -define([ - 'summernote/core/list', - 'summernote/core/dom', - 'summernote/core/key', - 'summernote/core/agent' -], function (list, dom, key, agent) { - var Clipboard = function (handler) { - var $paste; - - this.attach = function (layoutInfo) { - // [workaround] getting image from clipboard - // - IE11 and Firefox: CTRL+v hook - // - Webkit: event.clipboardData - if ((agent.isMSIE && agent.browserVersion > 10) || agent.isFF) { - $paste = $('<div />').attr('contenteditable', true).css({ - position : 'absolute', - left : -100000, - opacity : 0 - }); - - layoutInfo.editable().on('keydown', function (e) { - if (e.ctrlKey && e.keyCode === key.code.V) { - handler.invoke('saveRange', layoutInfo.editable()); - $paste.focus(); - - setTimeout(function () { - pasteByHook(layoutInfo); - }, 0); - } - }); - - layoutInfo.editable().before($paste); - } else { - 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 = $('<div />').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 (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); - } - }; - }; - - return Clipboard; -}); diff --git a/addons/web_editor/static/lib/summernote/src/js/module/Codeview.js b/addons/web_editor/static/lib/summernote/src/js/module/Codeview.js deleted file mode 100644 index 46f3d96ab12..00000000000 --- a/addons/web_editor/static/lib/summernote/src/js/module/Codeview.js +++ /dev/null @@ -1,137 +0,0 @@ -define([ - 'summernote/core/agent', - 'summernote/core/dom' -], function (agent, dom) { - - var CodeMirror; - if (agent.hasCodeMirror) { - if (agent.isSupportAmd) { - require(['CodeMirror'], function (cm) { - CodeMirror = cm; - }); - } else { - CodeMirror = window.CodeMirror; - } - } - - /** - * @class Codeview - */ - var Codeview = function (handler) { - - this.sync = function (layoutInfo) { - var isCodeview = handler.invoke('codeview.isActivated', layoutInfo); - if (isCodeview && agent.hasCodeMirror) { - layoutInfo.codable().data('cmEditor').save(); - } - }; - - /** - * @param {Object} layoutInfo - * @return {Boolean} - */ - this.isActivated = function (layoutInfo) { - var $editor = layoutInfo.editor(); - return $editor.hasClass('codeview'); - }; - - /** - * toggle codeview - * - * @param {Object} layoutInfo - */ - this.toggle = function (layoutInfo) { - if (this.isActivated(layoutInfo)) { - this.deactivate(layoutInfo); - } else { - this.activate(layoutInfo); - } - }; - - /** - * activate code view - * - * @param {Object} layoutInfo - */ - this.activate = function (layoutInfo) { - var $editor = layoutInfo.editor(), - $toolbar = layoutInfo.toolbar(), - $editable = layoutInfo.editable(), - $codable = layoutInfo.codable(), - $popover = layoutInfo.popover(), - $handle = layoutInfo.handle(); - - var options = $editor.data('options'); - - $codable.val(dom.html($editable, options.prettifyHtml)); - $codable.height($editable.height()); - - handler.invoke('toolbar.updateCodeview', $toolbar, true); - handler.invoke('popover.hide', $popover); - handler.invoke('handle.hide', $handle); - - $editor.addClass('codeview'); - - $codable.focus(); - - // activate CodeMirror as codable - if (agent.hasCodeMirror) { - var cmEditor = CodeMirror.fromTextArea($codable[0], options.codemirror); - - // CodeMirror TernServer - if (options.codemirror.tern) { - var server = new CodeMirror.TernServer(options.codemirror.tern); - cmEditor.ternServer = server; - cmEditor.on('cursorActivity', function (cm) { - server.updateArgHints(cm); - }); - } - - // CodeMirror hasn't Padding. - cmEditor.setSize(null, $editable.outerHeight()); - $codable.data('cmEditor', cmEditor); - } - }; - - /** - * deactivate code view - * - * @param {Object} layoutInfo - */ - this.deactivate = function (layoutInfo) { - var $holder = layoutInfo.holder(), - $editor = layoutInfo.editor(), - $toolbar = layoutInfo.toolbar(), - $editable = layoutInfo.editable(), - $codable = layoutInfo.codable(); - - var options = $editor.data('options'); - - // deactivate CodeMirror as codable - if (agent.hasCodeMirror) { - var cmEditor = $codable.data('cmEditor'); - $codable.val(cmEditor.getValue()); - cmEditor.toTextArea(); - } - - var value = dom.value($codable, options.prettifyHtml) || dom.emptyPara; - var isChange = $editable.html() !== value; - - $editable.html(value); - $editable.height(options.height ? $codable.height() : 'auto'); - $editor.removeClass('codeview'); - - if (isChange) { - handler.bindCustomEvent( - $holder, $editable.data('callbacks'), 'change' - )($editable.html(), $editable); - } - - $editable.focus(); - - handler.invoke('toolbar.updateCodeview', $toolbar, false); - }; - }; - - return Codeview; -}); diff --git a/addons/web_editor/static/lib/summernote/src/js/module/Dialog.js b/addons/web_editor/static/lib/summernote/src/js/module/Dialog.js new file mode 100644 index 00000000000..8e7cf07b323 --- /dev/null +++ b/addons/web_editor/static/lib/summernote/src/js/module/Dialog.js @@ -0,0 +1,158 @@ +define('summernote/module/Dialog', function () { + /** + * Dialog + * + * @class + */ + var Dialog = function () { + + /** + * toggle button status + * + * @param {jQuery} $btn + * @param {Boolean} isEnable + */ + var toggleBtn = function ($btn, isEnable) { + $btn.toggleClass('disabled', !isEnable); + $btn.attr('disabled', !isEnable); + }; + + /** + * show image dialog + * + * @param {jQuery} $editable + * @param {jQuery} $dialog + * @return {Promise} + */ + this.showImageDialog = function ($editable, $dialog) { + return $.Deferred(function (deferred) { + var $imageDialog = $dialog.find('.note-image-dialog'); + + var $imageInput = $dialog.find('.note-image-input'), + $imageUrl = $dialog.find('.note-image-url'), + $imageBtn = $dialog.find('.note-image-btn'); + + $imageDialog.one('shown.bs.modal', function () { + // Cloning imageInput to clear element. + $imageInput.replaceWith($imageInput.clone() + .on('change', function () { + deferred.resolve(this.files || this.value); + $imageDialog.modal('hide'); + }) + .val('') + ); + + $imageBtn.click(function (event) { + event.preventDefault(); + + deferred.resolve($imageUrl.val()); + $imageDialog.modal('hide'); + }); + + $imageUrl.on('keyup paste', function (event) { + var url; + + if (event.type === 'paste') { + url = event.originalEvent.clipboardData.getData('text'); + } else { + url = $imageUrl.val(); + } + + toggleBtn($imageBtn, url); + }).val('').trigger('focus'); + }).one('hidden.bs.modal', function () { + $imageInput.off('change'); + $imageUrl.off('keyup paste'); + $imageBtn.off('click'); + + if (deferred.state() === 'pending') { + deferred.reject(); + } + }).modal('show'); + }); + }; + + /** + * Show link dialog and set event handlers on dialog controls. + * + * @param {jQuery} $dialog + * @param {Object} linkInfo + * @return {Promise} + */ + this.showLinkDialog = function ($editable, $dialog, linkInfo) { + return $.Deferred(function (deferred) { + var $linkDialog = $dialog.find('.note-link-dialog'); + + var $linkText = $linkDialog.find('.note-link-text'), + $linkUrl = $linkDialog.find('.note-link-url'), + $linkBtn = $linkDialog.find('.note-link-btn'), + $openInNewWindow = $linkDialog.find('input[type=checkbox]'); + + $linkDialog.one('shown.bs.modal', function () { + $linkText.val(linkInfo.text); + + $linkText.on('input', function () { + // if linktext was modified by keyup, + // stop cloning text from linkUrl + linkInfo.text = $linkText.val(); + }); + + // if no url was given, copy text to url + if (!linkInfo.url) { + linkInfo.url = linkInfo.text; + toggleBtn($linkBtn, linkInfo.text); + } + + $linkUrl.on('input', function () { + toggleBtn($linkBtn, $linkUrl.val()); + // display same link on `Text to display` input + // when create a new link + if (!linkInfo.text) { + $linkText.val($linkUrl.val()); + } + }).val(linkInfo.url).trigger('focus').trigger('select'); + + $openInNewWindow.prop('checked', linkInfo.newWindow); + + $linkBtn.one('click', function (event) { + event.preventDefault(); + + deferred.resolve({ + range: linkInfo.range, + url: $linkUrl.val(), + text: $linkText.val(), + newWindow: $openInNewWindow.is(':checked') + }); + $linkDialog.modal('hide'); + }); + }).one('hidden.bs.modal', function () { + // detach events + $linkText.off('input'); + $linkUrl.off('input'); + $linkBtn.off('click'); + + if (deferred.state() === 'pending') { + deferred.reject(); + } + }).modal('show'); + }).promise(); + }; + + /** + * show help dialog + * + * @param {jQuery} $dialog + */ + this.showHelpDialog = function ($editable, $dialog) { + return $.Deferred(function (deferred) { + var $helpDialog = $dialog.find('.note-help-dialog'); + + $helpDialog.one('hidden.bs.modal', function () { + deferred.resolve(); + }).modal('show'); + }).promise(); + }; + }; + + return Dialog; +}); diff --git a/addons/web_editor/static/lib/summernote/src/js/module/DragAndDrop.js b/addons/web_editor/static/lib/summernote/src/js/module/DragAndDrop.js deleted file mode 100644 index b8871599c6f..00000000000 --- a/addons/web_editor/static/lib/summernote/src/js/module/DragAndDrop.js +++ /dev/null @@ -1,98 +0,0 @@ -define([ - 'summernote/core/dom' -], function (dom) { - var DragAndDrop = function (handler) { - var $document = $(document); - - /** - * attach Drag and Drop Events - * - * @param {Object} layoutInfo - layout Informations - * @param {Object} options - */ - this.attach = function (layoutInfo, options) { - if (options.airMode || options.disableDragAndDrop) { - // prevent default drop event - $document.on('drop', function (e) { - e.preventDefault(); - }); - } else { - this.attachDragAndDropEvent(layoutInfo, options); - } - }; - - /** - * attach Drag and Drop Events - * - * @param {Object} layoutInfo - layout Informations - * @param {Object} options - */ - this.attachDragAndDropEvent = function (layoutInfo, options) { - var collection = $(), - $editor = layoutInfo.editor(), - $dropzone = layoutInfo.dropzone(), - $dropzoneMessage = $dropzone.find('.note-dropzone-message'); - - // show dropzone on dragenter when dragging a object to document - // -but only if the editor is visible, i.e. has a positive width and height - $document.on('dragenter', function (e) { - var isCodeview = handler.invoke('codeview.isActivated', layoutInfo); - var hasEditorSize = $editor.width() > 0 && $editor.height() > 0; - if (!isCodeview && !collection.length && hasEditorSize) { - $editor.addClass('dragover'); - $dropzone.width($editor.width()); - $dropzone.height($editor.height()); - $dropzoneMessage.text(options.langInfo.image.dragImageHere); - } - collection = collection.add(e.target); - }).on('dragleave', function (e) { - collection = collection.not(e.target); - if (!collection.length) { - $editor.removeClass('dragover'); - } - }).on('drop', function () { - collection = $(); - $editor.removeClass('dragover'); - }); - - // change dropzone's message on hover. - $dropzone.on('dragenter', function () { - $dropzone.addClass('hover'); - $dropzoneMessage.text(options.langInfo.image.dropImage); - }).on('dragleave', function () { - $dropzone.removeClass('hover'); - $dropzoneMessage.text(options.langInfo.image.dragImageHere); - }); - - // attach dropImage - $dropzone.on('drop', function (event) { - - var dataTransfer = event.originalEvent.dataTransfer; - var layoutInfo = dom.makeLayoutInfo(event.currentTarget || event.target); - - if (dataTransfer && dataTransfer.files && dataTransfer.files.length) { - event.preventDefault(); - layoutInfo.editable().focus(); - handler.insertImages(layoutInfo, dataTransfer.files); - } else { - var insertNodefunc = function () { - layoutInfo.holder().summernote('insertNode', this); - }; - - for (var i = 0, len = dataTransfer.types.length; i < len; i++) { - var type = dataTransfer.types[i]; - var content = dataTransfer.getData(type); - - if (type.toLowerCase().indexOf('text') > -1) { - layoutInfo.holder().summernote('pasteHTML', content); - } else { - $(content).each(insertNodefunc); - } - } - } - }).on('dragover', false); // prevent default dragover event - }; - }; - - return DragAndDrop; -}); diff --git a/addons/web_editor/static/lib/summernote/src/js/module/Editor.js b/addons/web_editor/static/lib/summernote/src/js/module/Editor.js deleted file mode 100644 index 732072b5d24..00000000000 --- a/addons/web_editor/static/lib/summernote/src/js/module/Editor.js +++ /dev/null @@ -1,861 +0,0 @@ -define([ - 'summernote/core/agent', - 'summernote/core/func', - 'summernote/core/list', - 'summernote/core/dom', - 'summernote/core/range', - 'summernote/core/async', - 'summernote/editing/Style', - 'summernote/editing/Typing', - 'summernote/editing/Table', - 'summernote/editing/Bullet' -], function (agent, func, list, dom, range, async, - Style, Typing, Table, Bullet) { - - var KEY_BOGUS = 'bogus'; - - /** - * @class editing.Editor - * - * Editor - * - */ - var Editor = function (handler) { - - var self = this; - var style = new Style(); - var table = new Table(); - var typing = new Typing(); - var bullet = new Bullet(); - - this.style = style; // ODOO: allow access for override - this.table = table; // ODOO: allow access for override - this.typing = typing; // ODOO: allow access for override - this.bullet = bullet; // ODOO: allow access for override - - /** - * @method createRange - * - * create range - * - * @param {jQuery} $editable - * @return {WrappedRange} - */ - this.createRange = function ($editable) { - this.focus($editable); - return range.create(); - }; - - /** - * @method saveRange - * - * save current range - * - * @param {jQuery} $editable - * @param {Boolean} [thenCollapse=false] - */ - this.saveRange = function ($editable, thenCollapse) { - // ODOO: scroll to top when click on input in editable m (start_modification - // this.focus($editable); - var r = range.create(); - if (!r || ($editable[0] !== r.sc && !$.contains($editable[0], r.sc))) { - $editable.focus(); - } - // ODOO: end_modication) - $editable.data('range', range.create()); - if (thenCollapse) { - range.create().collapse().select(); - } - }; - - /** - * @method saveRange - * - * save current node list to $editable.data('childNodes') - * - * @param {jQuery} $editable - */ - this.saveNode = function ($editable) { - // copy child node reference - var copy = []; - for (var key = 0, len = $editable[0].childNodes.length; key < len; key++) { - copy.push($editable[0].childNodes[key]); - } - $editable.data('childNodes', copy); - }; - - /** - * @method restoreRange - * - * restore lately range - * - * @param {jQuery} $editable - */ - this.restoreRange = function ($editable) { - var rng = $editable.data('range'); - if (rng) { - rng.select(); - this.focus($editable); - } - }; - - /** - * @method restoreNode - * - * restore lately node list - * - * @param {jQuery} $editable - */ - this.restoreNode = function ($editable) { - $editable.html(''); - var child = $editable.data('childNodes'); - for (var index = 0, len = child.length; index < len; index++) { - $editable[0].appendChild(child[index]); - } - }; - - /** - * @method currentStyle - * - * current style - * - * @param {Node} target - * @return {Object|Boolean} unfocus - */ - this.currentStyle = function (target) { - var rng = range.create(); - var styleInfo = rng && rng.isOnEditable() ? style.current(rng.normalize()) : {}; - if (dom.isImg(target)) { - styleInfo.image = target; - } - return styleInfo; - }; - - /** - * style from node - * - * @param {jQuery} $node - * @return {Object} - */ - this.styleFromNode = function ($node) { - return style.fromNode($node); - }; - - var triggerOnBeforeChange = function ($editable) { - var $holder = dom.makeLayoutInfo($editable).holder(); - handler.bindCustomEvent( - $holder, $editable.data('callbacks'), 'before.command' - )($editable.html(), $editable); - }; - - var triggerOnChange = function ($editable) { - var $holder = dom.makeLayoutInfo($editable).holder(); - handler.bindCustomEvent( - $holder, $editable.data('callbacks'), 'change' - )($editable.html(), $editable); - }; - - /** - * @method undo - * undo - * @param {jQuery} $editable - */ - this.undo = function ($editable) { - triggerOnBeforeChange($editable); - $editable.data('NoteHistory').undo(); - triggerOnChange($editable); - }; - - /** - * @method redo - * redo - * @param {jQuery} $editable - */ - this.redo = function ($editable) { - triggerOnBeforeChange($editable); - $editable.data('NoteHistory').redo(); - triggerOnChange($editable); - }; - - /** - * @method beforeCommand - * before command - * @param {jQuery} $editable - */ - var beforeCommand = this.beforeCommand = function ($editable) { - triggerOnBeforeChange($editable); - // keep focus on editable before command execution - self.focus($editable); - }; - - /** - * @method afterCommand - * after command - * @param {jQuery} $editable - * @param {Boolean} isPreventTrigger - */ - var afterCommand = this.afterCommand = function ($editable, isPreventTrigger) { - $editable.data('NoteHistory').recordUndo(); - if (!isPreventTrigger) { - triggerOnChange($editable); - } - }; - - /** - * @method bold - * @param {jQuery} $editable - * @param {Mixed} value - */ - - /** - * @method italic - * @param {jQuery} $editable - * @param {Mixed} value - */ - - /** - * @method underline - * @param {jQuery} $editable - * @param {Mixed} value - */ - - /** - * @method strikethrough - * @param {jQuery} $editable - * @param {Mixed} value - */ - - /** - * @method formatBlock - * @param {jQuery} $editable - * @param {Mixed} value - */ - - /** - * @method superscript - * @param {jQuery} $editable - * @param {Mixed} value - */ - - /** - * @method subscript - * @param {jQuery} $editable - * @param {Mixed} value - */ - - /** - * @method justifyLeft - * @param {jQuery} $editable - * @param {Mixed} value - */ - - /** - * @method justifyCenter - * @param {jQuery} $editable - * @param {Mixed} value - */ - - /** - * @method justifyRight - * @param {jQuery} $editable - * @param {Mixed} value - */ - - /** - * @method justifyFull - * @param {jQuery} $editable - * @param {Mixed} value - */ - - /** - * @method formatBlock - * @param {jQuery} $editable - * @param {Mixed} value - */ - - /** - * @method removeFormat - * @param {jQuery} $editable - * @param {Mixed} value - */ - - /** - * @method backColor - * @param {jQuery} $editable - * @param {Mixed} value - */ - - /** - * @method foreColor - * @param {jQuery} $editable - * @param {Mixed} value - */ - - /** - * @method insertHorizontalRule - * @param {jQuery} $editable - * @param {Mixed} value - */ - - /** - * @method fontName - * - * change font name - * - * @param {jQuery} $editable - * @param {Mixed} value - */ - - /* jshint ignore:start */ - // native commands(with execCommand), generate function for execCommand - var commands = ['bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript', - 'justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull', - 'formatBlock', 'removeFormat', - 'backColor', 'foreColor', 'fontName']; - - for (var idx = 0, len = commands.length; idx < len; idx ++) { - this[commands[idx]] = (function (sCmd) { - return function ($editable, value) { - beforeCommand($editable); - - document.execCommand(sCmd, false, value); - - afterCommand($editable, true); - }; - })(commands[idx]); - } - /* jshint ignore:end */ - - /** - * @method tab - * - * handle tab key - * - * @param {jQuery} $editable - * @param {Object} options - */ - this.tab = function ($editable, options) { - var rng = this.createRange($editable); - if (rng.isCollapsed() && rng.isOnCell()) { - table.tab(rng); - } else { - beforeCommand($editable); - typing.insertTab($editable, rng, options.tabsize); - afterCommand($editable); - } - }; - - /** - * @method untab - * - * handle shift+tab key - * - */ - this.untab = function ($editable) { - var rng = this.createRange($editable); - if (rng.isCollapsed() && rng.isOnCell()) { - table.tab(rng, true); - } - }; - - /** - * @method insertParagraph - * - * insert paragraph - * - * @param {Node} $editable - */ - this.insertParagraph = function ($editable) { - beforeCommand($editable); - typing.insertParagraph($editable); - afterCommand($editable); - }; - - /** - * @method insertOrderedList - * - * @param {jQuery} $editable - */ - this.insertOrderedList = function ($editable) { - beforeCommand($editable); - bullet.insertOrderedList($editable); - afterCommand($editable); - }; - - /** - * @param {jQuery} $editable - */ - this.insertUnorderedList = function ($editable) { - beforeCommand($editable); - bullet.insertUnorderedList($editable); - afterCommand($editable); - }; - - /** - * @param {jQuery} $editable - */ - this.indent = function ($editable) { - beforeCommand($editable); - bullet.indent($editable); - afterCommand($editable); - }; - - /** - * @param {jQuery} $editable - */ - this.outdent = function ($editable) { - beforeCommand($editable); - bullet.outdent($editable); - afterCommand($editable); - }; - - /** - * insert image - * - * @param {jQuery} $editable - * @param {String} sUrl - */ - this.insertImage = function ($editable, sUrl, filename) { - async.createImage(sUrl, filename).then(function ($image) { - beforeCommand($editable); - $image.css({ - display: '', - width: Math.min($editable.width(), $image.width()) - }); - range.create().insertNode($image[0]); - range.createFromNodeAfter($image[0]).select(); - afterCommand($editable); - }).fail(function () { - var $holder = dom.makeLayoutInfo($editable).holder(); - handler.bindCustomEvent( - $holder, $editable.data('callbacks'), 'image.upload.error' - )(); - }); - }; - - /** - * @method insertNode - * insert node - * @param {Node} $editable - * @param {Node} node - */ - this.insertNode = function ($editable, node) { - beforeCommand($editable); - range.create().insertNode(node); - range.createFromNodeAfter(node).select(); - afterCommand($editable); - }; - - /** - * insert text - * @param {Node} $editable - * @param {String} text - */ - this.insertText = function ($editable, text) { - beforeCommand($editable); - var textNode = range.create().insertNode(dom.createText(text)); - range.create(textNode, dom.nodeLength(textNode)).select(); - afterCommand($editable); - }; - - /** - * paste HTML - * @param {Node} $editable - * @param {String} markup - */ - this.pasteHTML = function ($editable, markup) { - beforeCommand($editable); - var contents = range.create().pasteHTML(markup); - range.createFromNodeAfter(list.last(contents)).select(); - afterCommand($editable); - }; - - /** - * formatBlock - * - * @param {jQuery} $editable - * @param {String} tagName - */ - this.formatBlock = function ($editable, tagName) { - beforeCommand($editable); - // [workaround] for MSIE, IE need `<` - tagName = agent.isMSIE ? '<' + tagName + '>' : tagName; - document.execCommand('FormatBlock', false, tagName); - afterCommand($editable); - }; - - this.formatPara = function ($editable) { - beforeCommand($editable); - this.formatBlock($editable, 'P'); - afterCommand($editable); - }; - - /* jshint ignore:start */ - for (var idx = 1; idx <= 6; idx ++) { - this['formatH' + idx] = function (idx) { - return function ($editable) { - this.formatBlock($editable, 'H' + idx); - }; - }(idx); - }; - /* jshint ignore:end */ - - /** - * fontSize - * - * @param {jQuery} $editable - * @param {String} value - px - */ - this.fontSize = function ($editable, value) { - var rng = range.create(); - - if (rng.isCollapsed()) { - var spans = style.styleNodes(rng); - var firstSpan = list.head(spans); - - $(spans).css({ - 'font-size': value + 'px' - }); - - // [workaround] added styled bogus span for style - // - also bogus character needed for cursor position - if (firstSpan && !dom.nodeLength(firstSpan)) { - firstSpan.innerHTML = dom.ZERO_WIDTH_NBSP_CHAR; - range.createFromNodeAfter(firstSpan.firstChild).select(); - $editable.data(KEY_BOGUS, firstSpan); - } - } else { - beforeCommand($editable); - $(style.styleNodes(rng)).css({ - 'font-size': value + 'px' - }); - afterCommand($editable); - } - }; - - /** - * insert horizontal rule - * @param {jQuery} $editable - */ - this.insertHorizontalRule = function ($editable) { - beforeCommand($editable); - - var rng = range.create(); - var hrNode = rng.insertNode($('<HR/>')[0]); - if (hrNode.nextSibling) { - range.create(hrNode.nextSibling, 0).normalize().select(); - } - - afterCommand($editable); - }; - - /** - * remove bogus node and character - */ - this.removeBogus = function ($editable) { - var bogusNode = $editable.data(KEY_BOGUS); - if (!bogusNode) { - return; - } - - var textNode = list.find(list.from(bogusNode.childNodes), dom.isText); - - var bogusCharIdx = textNode.nodeValue.indexOf(dom.ZERO_WIDTH_NBSP_CHAR); - if (bogusCharIdx !== -1) { - textNode.deleteData(bogusCharIdx, 1); - } - - if (dom.isEmpty(bogusNode)) { - dom.remove(bogusNode); - } - - $editable.removeData(KEY_BOGUS); - }; - - /** - * lineHeight - * @param {jQuery} $editable - * @param {String} value - */ - this.lineHeight = function ($editable, value) { - beforeCommand($editable); - style.stylePara(range.create(), { - lineHeight: value - }); - afterCommand($editable); - }; - - /** - * unlink - * - * @type command - * - * @param {jQuery} $editable - */ - this.unlink = function ($editable) { - var rng = this.createRange($editable); - if (rng.isOnAnchor()) { - var anchor = dom.ancestor(rng.sc, dom.isAnchor); - rng = range.createFromNode(anchor); - rng.select(); - - beforeCommand($editable); - document.execCommand('unlink'); - afterCommand($editable); - } - }; - - /** - * create link (command) - * - * @param {jQuery} $editable - * @param {Object} linkInfo - * @param {Object} options - */ - this.createLink = function ($editable, linkInfo, options) { - var linkUrl = linkInfo.url; - var linkText = linkInfo.text; - var isNewWindow = linkInfo.isNewWindow; - var className = linkInfo.className || null; // ODOO: addition - var rng = linkInfo.range || this.createRange($editable); - var isTextChanged = rng.toString() !== linkText; - - options = options || dom.makeLayoutInfo($editable).editor().data('options'); - - beforeCommand($editable); - - if (options.onCreateLink) { - linkUrl = options.onCreateLink(linkUrl); - } - - var anchors = []; - if (isTextChanged) { - // Create a new link when text changed. - var anchor = rng.insertNode($('<A>' + linkText + '</A>')[0]); - anchors.push(anchor); - } else { - anchors = style.styleNodes(rng, { - nodeName: 'A', - expandClosestSibling: true, - onlyPartialContains: true - }); - } - - $.each(anchors, function (idx, anchor) { - $(anchor).attr('href', linkUrl); - $(anchor).attr('class', className); // ODOO: addition - if (isNewWindow) { - $(anchor).attr('target', '_blank'); - } else { - $(anchor).removeAttr('target'); - } - }); - - var startRange = range.createFromNodeBefore(list.head(anchors)); - var startPoint = startRange.getStartPoint(); - var endRange = range.createFromNodeAfter(list.last(anchors)); - var endPoint = endRange.getEndPoint(); - - range.create( - startPoint.node, - startPoint.offset, - endPoint.node, - endPoint.offset - ).select(); - - afterCommand($editable); - }; - - /** - * returns link info - * - * @return {Object} - * @return {WrappedRange} return.range - * @return {String} return.text - * @return {Boolean} [return.isNewWindow=true] - * @return {String} [return.url=""] - */ - this.getLinkInfo = function ($editable) { - this.focus($editable); - - var rng = range.create().expand(dom.isAnchor); - - // Get the first anchor on range(for edit). - var $anchor = $(list.head(rng.nodes(dom.isAnchor))); - - return { - range: rng, - text: rng.toString(), - isNewWindow: $anchor.length ? $anchor.attr('target') === '_blank' : false, - url: $anchor.length ? $anchor.attr('href') : '' - }; - }; - - /** - * setting color - * - * @param {Node} $editable - * @param {Object} sObjColor color code - * @param {String} sObjColor.foreColor foreground color - * @param {String} sObjColor.backColor background color - */ - this.color = function ($editable, sObjColor) { - var oColor = JSON.parse(sObjColor); - var foreColor = oColor.foreColor, backColor = oColor.backColor; - - beforeCommand($editable); - - if (foreColor) { document.execCommand('foreColor', false, foreColor); } - if (backColor) { document.execCommand('backColor', false, backColor); } - - afterCommand($editable); - }; - - /** - * insert Table - * - * @param {Node} $editable - * @param {String} sDim dimension of table (ex : "5x5") - */ - this.insertTable = function ($editable, sDim) { - var dimension = sDim.split('x'); - beforeCommand($editable); - - var rng = range.create().deleteContents(); - rng.insertNode(table.createTable(dimension[0], dimension[1])); - afterCommand($editable); - }; - - /** - * float me - * - * @param {jQuery} $editable - * @param {String} value - * @param {jQuery} $target - */ - this.floatMe = function ($editable, value, $target) { - beforeCommand($editable); - // bootstrap - $target.removeClass('pull-left pull-right'); - if (value && value !== 'none') { - $target.addClass('pull-' + value); - } - - // fallback for non-bootstrap - $target.css('float', value); - afterCommand($editable); - }; - - /** - * change image shape - * - * @param {jQuery} $editable - * @param {String} value css class - * @param {Node} $target - */ - this.imageShape = function ($editable, value, $target) { - beforeCommand($editable); - - $target.removeClass('img-rounded img-circle img-thumbnail'); - - if (value) { - $target.addClass(value); - } - - afterCommand($editable); - }; - - /** - * resize overlay element - * @param {jQuery} $editable - * @param {String} value - * @param {jQuery} $target - target element - */ - this.resize = function ($editable, value, $target) { - beforeCommand($editable); - - $target.css({ - width: value * 100 + '%', - height: '' - }); - - afterCommand($editable); - }; - - /** - * @param {Position} pos - * @param {jQuery} $target - target element - * @param {Boolean} [bKeepRatio] - keep ratio - */ - this.resizeTo = function (pos, $target, bKeepRatio) { - var imageSize; - if (bKeepRatio) { - var newRatio = pos.y / pos.x; - var ratio = $target.data('ratio'); - imageSize = { - width: ratio > newRatio ? pos.x : pos.y / ratio, - height: ratio > newRatio ? pos.x * ratio : pos.y - }; - } else { - imageSize = { - width: pos.x, - height: pos.y - }; - } - - $target.css(imageSize); - }; - - /** - * remove media object - * - * @param {jQuery} $editable - * @param {String} value - dummy argument (for keep interface) - * @param {jQuery} $target - target element - */ - this.removeMedia = function ($editable, value, $target) { - beforeCommand($editable); - $target.detach(); - - handler.bindCustomEvent( - $(), $editable.data('callbacks'), 'media.delete' - )($target, $editable); - - afterCommand($editable); - }; - - /** - * set focus - * - * @param $editable - */ - this.focus = function ($editable) { - $editable.focus(); - - // [workaround] for firefox bug http://goo.gl/lVfAaI - if (agent.isFF) { - var rng = range.create(); - if (!rng || rng.isOnEditable()) { - return; - } - - range.createFromNode($editable[0]) - .normalize() - .collapse() - .select(); - } - }; - - /** - * returns whether contents is empty or not. - * - * @param {jQuery} $editable - * @return {Boolean} - */ - this.isEmpty = function ($editable) { - return dom.isEmpty($editable[0]) || dom.emptyPara === $editable.html(); - }; - }; - - return Editor; -}); diff --git a/addons/web_editor/static/lib/summernote/src/js/module/Fullscreen.js b/addons/web_editor/static/lib/summernote/src/js/module/Fullscreen.js deleted file mode 100644 index fd837483b3b..00000000000 --- a/addons/web_editor/static/lib/summernote/src/js/module/Fullscreen.js +++ /dev/null @@ -1,51 +0,0 @@ -define([], function () { // ODOO: remove error from Odoo define - var Fullscreen = function (handler) { - var $window = $(window); - var $scrollbar = $('html, body'); - - /** - * toggle fullscreen - * - * @param {Object} layoutInfo - */ - this.toggle = function (layoutInfo) { - - var $editor = layoutInfo.editor(), - $toolbar = layoutInfo.toolbar(), - $editable = layoutInfo.editable(), - $codable = layoutInfo.codable(); - - var resize = function (size) { - $editable.css('height', size.h); - $codable.css('height', size.h); - if ($codable.data('cmeditor')) { - $codable.data('cmeditor').setsize(null, size.h); - } - }; - - $editor.toggleClass('fullscreen'); - var isFullscreen = $editor.hasClass('fullscreen'); - if (isFullscreen) { - $editable.data('orgheight', $editable.css('height')); - - $window.on('resize', function () { - resize({ - h: $window.height() - $toolbar.outerHeight() - }); - }).trigger('resize'); - - $scrollbar.css('overflow', 'hidden'); - } else { - $window.off('resize'); - resize({ - h: $editable.data('orgheight') - }); - $scrollbar.css('overflow', 'visible'); - } - - handler.invoke('toolbar.updateFullscreen', $toolbar, isFullscreen); - }; - }; - - return Fullscreen; -}); diff --git a/addons/web_editor/static/lib/summernote/src/js/module/Handle.js b/addons/web_editor/static/lib/summernote/src/js/module/Handle.js index 3b62304d706..375428faa32 100644 --- a/addons/web_editor/static/lib/summernote/src/js/module/Handle.js +++ b/addons/web_editor/static/lib/summernote/src/js/module/Handle.js @@ -1,60 +1,8 @@ -define([ - 'summernote/core/dom' -], function (dom) { +define('summernote/module/Handle', function () { /** - * @class module.Handle - * * Handle */ - var Handle = function (handler) { - var $document = $(document); - - /** - * `mousedown` event handler on $handle - * - controlSizing: resize image - * - * @param {MouseEvent} event - */ - var hHandleMousedown = function (event) { - if (dom.isControlSizing(event.target)) { - event.preventDefault(); - event.stopPropagation(); - - var layoutInfo = dom.makeLayoutInfo(event.target), - $handle = layoutInfo.handle(), - $popover = layoutInfo.popover(), - $editable = layoutInfo.editable(), - $editor = layoutInfo.editor(); - - var target = $handle.find('.note-control-selection').data('target'), - $target = $(target), posStart = $target.offset(), - scrollTop = $document.scrollTop(); - - var isAirMode = $editor.data('options').airMode; - - $document.on('mousemove', function (event) { - handler.invoke('editor.resizeTo', { - x: event.clientX - posStart.left, - y: event.clientY - (posStart.top - scrollTop) - }, $target, !event.shiftKey); - - handler.invoke('handle.update', $handle, {image: target}, isAirMode); - handler.invoke('popover.update', $popover, {image: target}, isAirMode); - }).one('mouseup', function () { - $document.off('mousemove'); - handler.invoke('editor.afterCommand', $editable); - }); - - if (!$target.data('ratio')) { // original ratio. - $target.data('ratio', $target.height() / $target.width()); - } - } - }; - - this.attach = function (layoutInfo) { - layoutInfo.handle().on('mousedown', hHandleMousedown); - }; - + var Handle = function () { /** * update handle * @param {jQuery} $handle @@ -87,11 +35,6 @@ define([ } }; - /** - * hide - * - * @param {jQuery} $handle - */ this.hide = function ($handle) { $handle.children().hide(); }; diff --git a/addons/web_editor/static/lib/summernote/src/js/module/HelpDialog.js b/addons/web_editor/static/lib/summernote/src/js/module/HelpDialog.js deleted file mode 100644 index e4ff7b775ed..00000000000 --- a/addons/web_editor/static/lib/summernote/src/js/module/HelpDialog.js +++ /dev/null @@ -1,35 +0,0 @@ -define([], function () { // ODOO: remove error from Odoo define - var HelpDialog = function (handler) { - /** - * show help dialog - * - * @param {jQuery} $editable - * @param {jQuery} $dialog - * @return {Promise} - */ - this.showHelpDialog = function ($editable, $dialog) { - return $.Deferred(function (deferred) { - var $helpDialog = $dialog.find('.note-help-dialog'); - - $helpDialog.one('hidden.bs.modal', function () { - deferred.resolve(); - }).modal('show'); - }).promise(); - }; - - /** - * @param {Object} layoutInfo - */ - this.show = function (layoutInfo) { - var $dialog = layoutInfo.dialog(), - $editable = layoutInfo.editable(); - - handler.invoke('editor.saveRange', $editable, true); - this.showHelpDialog($editable, $dialog).then(function () { - handler.invoke('editor.restoreRange', $editable); - }); - }; - }; - - return HelpDialog; -}); diff --git a/addons/web_editor/static/lib/summernote/src/js/module/ImageDialog.js b/addons/web_editor/static/lib/summernote/src/js/module/ImageDialog.js deleted file mode 100644 index 042921183cc..00000000000 --- a/addons/web_editor/static/lib/summernote/src/js/module/ImageDialog.js +++ /dev/null @@ -1,110 +0,0 @@ -define([ - 'summernote/core/key' -], function (key) { - var ImageDialog = function (handler) { - /** - * toggle button status - * - * @private - * @param {jQuery} $btn - * @param {Boolean} isEnable - */ - var toggleBtn = function ($btn, isEnable) { - $btn.toggleClass('disabled', !isEnable); - $btn.attr('disabled', !isEnable); - }; - - /** - * bind enter key - * - * @private - * @param {jQuery} $input - * @param {jQuery} $btn - */ - var bindEnterKey = function ($input, $btn) { - $input.on('keypress', function (event) { - if (event.keyCode === key.code.ENTER) { - $btn.trigger('click'); - } - }); - }; - - this.show = function (layoutInfo) { - var $dialog = layoutInfo.dialog(), - $editable = layoutInfo.editable(); - - handler.invoke('editor.saveRange', $editable); - this.showImageDialog($editable, $dialog).then(function (data) { - handler.invoke('editor.restoreRange', $editable); - - if (typeof data === 'string') { - // image url - handler.invoke('editor.insertImage', $editable, data); - } else { - // array of files - handler.insertImages(layoutInfo, data); - } - }).fail(function () { - handler.invoke('editor.restoreRange', $editable); - }); - }; - - /** - * show image dialog - * - * @param {jQuery} $editable - * @param {jQuery} $dialog - * @return {Promise} - */ - this.showImageDialog = function ($editable, $dialog) { - return $.Deferred(function (deferred) { - var $imageDialog = $dialog.find('.note-image-dialog'); - - var $imageInput = $dialog.find('.note-image-input'), - $imageUrl = $dialog.find('.note-image-url'), - $imageBtn = $dialog.find('.note-image-btn'); - - $imageDialog.one('shown.bs.modal', function () { - // Cloning imageInput to clear element. - $imageInput.replaceWith($imageInput.clone() - .on('change', function () { - deferred.resolve(this.files || this.value); - $imageDialog.modal('hide'); - }) - .val('') - ); - - $imageBtn.click(function (event) { - event.preventDefault(); - - deferred.resolve($imageUrl.val()); - $imageDialog.modal('hide'); - }); - - $imageUrl.on('keyup paste', function (event) { - var url; - - if (event.type === 'paste') { - url = event.originalEvent.clipboardData.getData('text'); - } else { - url = $imageUrl.val(); - } - - toggleBtn($imageBtn, url); - }).val('').trigger('focus'); - bindEnterKey($imageUrl, $imageBtn); - }).one('hidden.bs.modal', function () { - $imageInput.off('change'); - $imageUrl.off('keyup paste keypress'); - $imageBtn.off('click'); - - if (deferred.state() === 'pending') { - deferred.reject(); - } - }).modal('show'); - }); - }; - }; - - return ImageDialog; -}); diff --git a/addons/web_editor/static/lib/summernote/src/js/module/LinkDialog.js b/addons/web_editor/static/lib/summernote/src/js/module/LinkDialog.js deleted file mode 100644 index 045119ee68a..00000000000 --- a/addons/web_editor/static/lib/summernote/src/js/module/LinkDialog.js +++ /dev/null @@ -1,129 +0,0 @@ -define([ - 'summernote/core/key' -], function (key) { - var LinkDialog = function (handler) { - - /** - * toggle button status - * - * @private - * @param {jQuery} $btn - * @param {Boolean} isEnable - */ - var toggleBtn = function ($btn, isEnable) { - $btn.toggleClass('disabled', !isEnable); - $btn.attr('disabled', !isEnable); - }; - - /** - * bind enter key - * - * @private - * @param {jQuery} $input - * @param {jQuery} $btn - */ - var bindEnterKey = function ($input, $btn) { - $input.on('keypress', function (event) { - if (event.keyCode === key.code.ENTER) { - $btn.trigger('click'); - } - }); - }; - - /** - * Show link dialog and set event handlers on dialog controls. - * - * @param {jQuery} $editable - * @param {jQuery} $dialog - * @param {Object} linkInfo - * @return {Promise} - */ - this.showLinkDialog = function ($editable, $dialog, linkInfo) { - return $.Deferred(function (deferred) { - var $linkDialog = $dialog.find('.note-link-dialog'); - - var $linkText = $linkDialog.find('.note-link-text'), - $linkUrl = $linkDialog.find('.note-link-url'), - $linkBtn = $linkDialog.find('.note-link-btn'), - $openInNewWindow = $linkDialog.find('input[type=checkbox]'); - - $linkDialog.one('shown.bs.modal', function () { - $linkText.val(linkInfo.text); - - $linkText.on('input', function () { - toggleBtn($linkBtn, $linkText.val() && $linkUrl.val()); - // if linktext was modified by keyup, - // stop cloning text from linkUrl - linkInfo.text = $linkText.val(); - }); - - // if no url was given, copy text to url - if (!linkInfo.url) { - linkInfo.url = linkInfo.text || 'http://'; - toggleBtn($linkBtn, linkInfo.text); - } - - $linkUrl.on('input', function () { - toggleBtn($linkBtn, $linkText.val() && $linkUrl.val()); - // display same link on `Text to display` input - // when create a new link - if (!linkInfo.text) { - $linkText.val($linkUrl.val()); - } - }).val(linkInfo.url).trigger('focus').trigger('select'); - - bindEnterKey($linkUrl, $linkBtn); - bindEnterKey($linkText, $linkBtn); - - $openInNewWindow.prop('checked', linkInfo.isNewWindow); - - $linkBtn.one('click', function (event) { - event.preventDefault(); - - deferred.resolve({ - range: linkInfo.range, - url: $linkUrl.val(), - text: $linkText.val(), - isNewWindow: $openInNewWindow.is(':checked') - }); - $linkDialog.modal('hide'); - }); - }).one('hidden.bs.modal', function () { - // detach events - $linkText.off('input keypress'); - $linkUrl.off('input keypress'); - $linkBtn.off('click'); - - if (deferred.state() === 'pending') { - deferred.reject(); - } - }).modal('show'); - }).promise(); - }; - - /** - * @param {Object} layoutInfo - */ - this.show = function (layoutInfo) { - var $editor = layoutInfo.editor(), - $dialog = layoutInfo.dialog(), - $editable = layoutInfo.editable(), - $popover = layoutInfo.popover(), - linkInfo = handler.invoke('editor.getLinkInfo', $editable); - - var options = $editor.data('options'); - - handler.invoke('editor.saveRange', $editable); - this.showLinkDialog($editable, $dialog, linkInfo).then(function (linkInfo) { - handler.invoke('editor.restoreRange', $editable); - handler.invoke('editor.createLink', $editable, linkInfo, options); - // hide popover after creating link - handler.invoke('popover.hide', $popover); - }).fail(function () { - handler.invoke('editor.restoreRange', $editable); - }); - }; - }; - - return LinkDialog; -}); diff --git a/addons/web_editor/static/lib/summernote/src/js/module/Popover.js b/addons/web_editor/static/lib/summernote/src/js/module/Popover.js index f669569f6ba..e0531f06c45 100644 --- a/addons/web_editor/static/lib/summernote/src/js/module/Popover.js +++ b/addons/web_editor/static/lib/summernote/src/js/module/Popover.js @@ -4,32 +4,22 @@ define([ 'summernote/module/Button' ], function (func, list, Button) { /** - * @class module.Popover - * * Popover (http://getbootstrap.com/javascript/#popovers) - * */ var Popover = function () { var button = new Button(); - this.button = button; // ODOO: allow access for override + this.button = button; // odoo change for overwrite /** * returns position from placeholder - * - * @private * @param {Node} placeholder - * @param {Object} options - * @param {Boolean} options.isAirMode - * @return {Position} + * @param {Boolean} isAirMode */ - var posFromPlaceholder = function (placeholder, options) { - var isAirMode = options && options.isAirMode; - var isLeftTop = options && options.isLeftTop; - + var posFromPlaceholder = function (placeholder, isAirMode) { var $placeholder = $(placeholder); var pos = isAirMode ? $placeholder.offset() : $placeholder.position(); - var height = isLeftTop ? 0 : $placeholder.outerHeight(true); // include margin + var height = $placeholder.outerHeight(true); // include margin // popover below placeholder. return { @@ -40,8 +30,6 @@ define([ /** * show popover - * - * @private * @param {jQuery} popover * @param {Position} pos */ @@ -68,32 +56,21 @@ define([ if (styleInfo.anchor) { var $anchor = $linkPopover.find('a'); var href = $(styleInfo.anchor).attr('href'); - var target = $(styleInfo.anchor).attr('target'); $anchor.attr('href', href).html(href); - if (!target) { - $anchor.removeAttr('target'); - } else { - $anchor.attr('target', '_blank'); - } - showPopover($linkPopover, posFromPlaceholder(styleInfo.anchor, { - isAirMode: isAirMode - })); + showPopover($linkPopover, posFromPlaceholder(styleInfo.anchor, isAirMode)); } else { $linkPopover.hide(); } var $imagePopover = $popover.find('.note-image-popover'); if (styleInfo.image) { - showPopover($imagePopover, posFromPlaceholder(styleInfo.image, { - isAirMode: isAirMode, - isLeftTop: true - })); + showPopover($imagePopover, posFromPlaceholder(styleInfo.image, isAirMode)); } else { $imagePopover.hide(); } var $airPopover = $popover.find('.note-air-popover'); - if (isAirMode && styleInfo.range && !styleInfo.range.isCollapsed()) { + if (isAirMode && !styleInfo.range.isCollapsed()) { var rect = list.last(styleInfo.range.getClientRects()); if (rect) { var bnd = func.rect2bnd(rect); diff --git a/addons/web_editor/static/lib/summernote/src/js/module/Statusbar.js b/addons/web_editor/static/lib/summernote/src/js/module/Statusbar.js deleted file mode 100644 index b50fce458eb..00000000000 --- a/addons/web_editor/static/lib/summernote/src/js/module/Statusbar.js +++ /dev/null @@ -1,44 +0,0 @@ -define([ - 'summernote/core/dom' -], function (dom) { - var EDITABLE_PADDING = 24; - - var Statusbar = function () { - var $document = $(document); - - this.attach = function (layoutInfo, options) { - if (!options.disableResizeEditor) { - layoutInfo.statusbar().on('mousedown', hStatusbarMousedown); - } - }; - - /** - * `mousedown` event handler on statusbar - * - * @param {MouseEvent} event - */ - var hStatusbarMousedown = function (event) { - event.preventDefault(); - event.stopPropagation(); - - var $editable = dom.makeLayoutInfo(event.target).editable(); - var editableTop = $editable.offset().top - $document.scrollTop(); - - var layoutInfo = dom.makeLayoutInfo(event.currentTarget || event.target); - var options = layoutInfo.editor().data('options'); - - $document.on('mousemove', function (event) { - var nHeight = event.clientY - (editableTop + EDITABLE_PADDING); - - nHeight = (options.minHeight > 0) ? Math.max(nHeight, options.minHeight) : nHeight; - nHeight = (options.maxHeight > 0) ? Math.min(nHeight, options.maxHeight) : nHeight; - - $editable.height(nHeight); - }).one('mouseup', function () { - $document.off('mousemove'); - }); - }; - }; - - return Statusbar; -}); diff --git a/addons/web_editor/static/lib/summernote/src/js/module/Toolbar.js b/addons/web_editor/static/lib/summernote/src/js/module/Toolbar.js index 5e5e5721b63..832b7c4c561 100644 --- a/addons/web_editor/static/lib/summernote/src/js/module/Toolbar.js +++ b/addons/web_editor/static/lib/summernote/src/js/module/Toolbar.js @@ -1,17 +1,14 @@ define([ 'summernote/core/list', - 'summernote/core/dom', 'summernote/module/Button' -], function (list, dom, Button) { +], function (list, Button) { /** - * @class module.Toolbar - * * Toolbar */ var Toolbar = function () { var button = new Button(); - this.button = button; // ODOO: allow access for override + this.button = button; // odoo change for overwrite this.update = function ($toolbar, styleInfo) { button.update($toolbar, styleInfo); @@ -46,54 +43,14 @@ define([ .addClass('disabled'); }; - /** - * @param {jQuery} $container - * @param {Boolean} [bFullscreen=false] - */ this.updateFullscreen = function ($container, bFullscreen) { var $btn = $container.find('button[data-event="fullscreen"]'); $btn.toggleClass('active', bFullscreen); }; - /** - * @param {jQuery} $container - * @param {Boolean} [isCodeview=false] - */ this.updateCodeview = function ($container, isCodeview) { var $btn = $container.find('button[data-event="codeview"]'); $btn.toggleClass('active', isCodeview); - - if (isCodeview) { - this.deactivate($container); - } else { - this.activate($container); - } - }; - - /** - * get button in toolbar - * - * @param {jQuery} $editable - * @param {String} name - * @return {jQuery} - */ - this.get = function ($editable, name) { - var $toolbar = dom.makeLayoutInfo($editable).toolbar(); - - return $toolbar.find('[data-name=' + name + ']'); - }; - - /** - * set button state - * @param {jQuery} $editable - * @param {String} name - * @param {Boolean} [isActive=true] - */ - this.setButtonState = function ($editable, name, isActive) { - isActive = (isActive === false) ? false : true; - - var $button = this.get($editable, name); - $button.toggleClass('active', isActive); }; }; diff --git a/addons/web_editor/static/lib/summernote/src/js/defaults.js b/addons/web_editor/static/lib/summernote/src/js/settings.js index f7b0c0c6379..25d1bdf809a 100644 --- a/addons/web_editor/static/lib/summernote/src/js/defaults.js +++ b/addons/web_editor/static/lib/summernote/src/js/settings.js @@ -1,36 +1,10 @@ -define('summernote/defaults', function () { - /** - * @class defaults - * - * @singleton - */ - var defaults = { - /** @property */ +define('summernote/settings', function () { + var settings = { + // version version: '@VERSION', /** - * - * for event options, reference to EventHandler.attach - * - * @property {Object} options - * @property {String/Number} [options.width=null] set editor width - * @property {String/Number} [options.height=null] set editor height, ex) 300 - * @property {String/Number} options.minHeight set minimum height of editor - * @property {String/Number} options.maxHeight - * @property {String/Number} options.focus - * @property {Number} options.tabsize - * @property {Boolean} options.styleWithSpan - * @property {Object} options.codemirror - * @property {Object} [options.codemirror.mode='text/html'] - * @property {Object} [options.codemirror.htmlMode=true] - * @property {Object} [options.codemirror.lineNumbers=true] - * @property {String} [options.lang=en-US] language 'en-US', 'ko-KR', ... - * @property {String} [options.direction=null] text direction, ex) 'rtl' - * @property {Array} [options.toolbar] - * @property {Boolean} [options.airMode=false] - * @property {Array} [options.airPopover] - * @property {Fucntion} [options.onInit] initialize - * @property {Fucntion} [options.onsubmit] + * options */ options: { width: null, // set editor width @@ -47,84 +21,10 @@ define('summernote/defaults', function () { disableLinkTarget: false, // hide link Target Checkbox disableDragAndDrop: false, // disable drag and drop event disableResizeEditor: false, // disable resizing editor - disableResizeImage: false, // disable resizing image shortcuts: true, // enable keyboard shortcuts - textareaAutoSync: true, // enable textarea auto sync - placeholder: false, // enable placeholder text - prettifyHtml: true, // enable prettifying html while toggling codeview - - iconPrefix: 'fa fa-', // prefix for css icon classes - - icons: { - font: { - bold: 'bold', - italic: 'italic', - underline: 'underline', - clear: 'eraser', - height: 'text-height', - strikethrough: 'strikethrough', - superscript: 'superscript', - subscript: 'subscript' - }, - image: { - image: 'picture-o', - floatLeft: 'align-left', - floatRight: 'align-right', - floatNone: 'align-justify', - shapeRounded: 'square', - shapeCircle: 'circle-o', - shapeThumbnail: 'picture-o', - shapeNone: 'times', - remove: 'trash-o' - }, - link: { - link: 'link', - unlink: 'unlink', - edit: 'edit' - }, - table: { - table: 'table' - }, - hr: { - insert: 'minus' - }, - style: { - style: 'magic' - }, - lists: { - unordered: 'list-ul', - ordered: 'list-ol' - }, - options: { - help: 'question', - fullscreen: 'arrows-alt', - codeview: 'code' - }, - paragraph: { - paragraph: 'align-left', - outdent: 'outdent', - indent: 'indent', - left: 'align-left', - center: 'align-center', - right: 'align-right', - justify: 'align-justify' - }, - color: { - recent: 'font' - }, - history: { - undo: 'undo', - redo: 'repeat' - }, - misc: { - check: 'check' - } - }, - - dialogsInBody: false, // false will add dialogs into editor codemirror: { // codemirror options mode: 'text/html', @@ -140,9 +40,7 @@ define('summernote/defaults', function () { toolbar: [ ['style', ['style']], ['font', ['bold', 'italic', 'underline', 'clear']], - // ['font', ['bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript', 'clear']], ['fontname', ['fontname']], - ['fontsize', ['fontsize']], ['color', ['color']], ['para', ['ul', 'ol', 'paragraph']], ['height', ['height']], @@ -152,8 +50,6 @@ define('summernote/defaults', function () { ['help', ['help']] ], - plugin : { }, - // air mode: inline editor airMode: false, // airPopover: [ @@ -184,12 +80,9 @@ define('summernote/defaults', function () { // fontName fontNames: [ 'Arial', 'Arial Black', 'Comic Sans MS', 'Courier New', - 'Helvetica Neue', 'Helvetica', 'Impact', 'Lucida Grande', + 'Helvetica Neue', 'Impact', 'Lucida Grande', 'Tahoma', 'Times New Roman', 'Verdana' ], - fontNamesIgnoreCheck: [], - - fontSizes: ['8', '9', '10', '11', '12', '14', '18', '24', '36'], // pallete colors(n x n) colors: [ @@ -224,7 +117,6 @@ define('summernote/defaults', function () { onkeydown: null, // keydown onImageUpload: null, // imageUpload onImageUploadError: null, // imageUploadError - onMediaDelete: null, // media delete onToolbarClick: null, onsubmit: null, @@ -236,6 +128,8 @@ define('summernote/defaults', function () { onCreateLink: function (sLinkUrl) { if (sLinkUrl.indexOf('@') !== -1 && sLinkUrl.indexOf(':') === -1) { sLinkUrl = 'mailto:' + sLinkUrl; + } else if (sLinkUrl.indexOf('://') === -1) { + sLinkUrl = 'http://' + sLinkUrl; } return sLinkUrl; @@ -313,11 +207,7 @@ define('summernote/defaults', function () { underline: 'Underline', clear: 'Remove Font Style', height: 'Line Height', - name: 'Font Family', - strikethrough: 'Strikethrough', - subscript: 'Subscript', - superscript: 'Superscript', - size: 'Font Size' + name: 'Font Family' }, image: { image: 'Picture', @@ -401,8 +291,7 @@ define('summernote/defaults', function () { textFormatting: 'Text formatting', action: 'Action', paragraphFormatting: 'Paragraph formatting', - documentStyle: 'Document Style', - extraKeys: 'Extra keys' + documentStyle: 'Document Style' }, history: { undo: 'Undo', @@ -412,5 +301,5 @@ define('summernote/defaults', function () { } }; - return defaults; + return settings; }); diff --git a/addons/web_editor/static/lib/summernote/src/js/summernote.js b/addons/web_editor/static/lib/summernote/src/js/summernote.js index 329ba7fa126..791a9452e85 100644 --- a/addons/web_editor/static/lib/summernote/src/js/summernote.js +++ b/addons/web_editor/static/lib/summernote/src/js/summernote.js @@ -1,140 +1,39 @@ define([ - 'summernote/core/agent', - 'summernote/core/list', - 'summernote/core/dom', + 'summernote/core/agent', 'summernote/core/dom', 'summernote/core/range', - 'summernote/defaults', - 'summernote/EventHandler', - 'summernote/Renderer', - 'summernote/core/key' // ODOO: change for override -], function (agent, list, dom, range, - defaults, EventHandler, Renderer, key) { - + 'summernote/settings', + 'summernote/EventHandler', 'summernote/Renderer', + 'summernote/core/list', // odoo change for overwrite + 'summernote/core/key' // odoo change for overwrite +], function (agent, dom, range, settings, EventHandler, Renderer, list, key) { // odoo change for overwrite // jQuery namespace for summernote - /** - * @class $.summernote - * - * summernote attribute - * - * @mixin defaults - * @singleton - * - */ $.summernote = $.summernote || {}; - // extends default settings - // - $.summernote.version - // - $.summernote.options - // - $.summernote.lang - $.extend($.summernote, defaults); + // extends default `settings` + $.extend($.summernote, settings); var renderer = new Renderer(); var eventHandler = new EventHandler(); $.extend($.summernote, { - /** @property {Renderer} */ renderer: renderer, - /** @property {EventHandler} */ eventHandler: eventHandler, - /** - * @property {Object} core - * @property {core.agent} core.agent - * @property {core.dom} core.dom - * @property {core.range} core.range - */ core: { agent: agent, - list : list, dom: dom, range: range, - key: key // ODOO: change for override + list: list, // odoo change for overwrite + key: key // odoo change for overwrite }, - /** - * @property {Object} - * pluginEvents event list for plugins - * event has name and callback function. - * - * ``` - * $.summernote.addPlugin({ - * events : { - * 'hello' : function(layoutInfo, value, $target) { - * console.log('event name is hello, value is ' + value ); - * } - * } - * }) - * ``` - * - * * event name is data-event property. - * * layoutInfo is a summernote layout information. - * * value is data-value property. - */ - pluginEvents: {}, - - plugins : [] + pluginEvents: {} }); /** - * @method addPlugin - * - * add Plugin in Summernote - * - * Summernote can make a own plugin. + * addPlugin * - * ### Define plugin - * ``` - * // get template function - * var tmpl = $.summernote.renderer.getTemplate(); - * - * // add a button - * $.summernote.addPlugin({ - * buttons : { - * // "hello" is button's namespace. - * "hello" : function(lang, options) { - * // make icon button by template function - * return tmpl.iconButton(options.iconPrefix + 'header', { - * // callback function name when button clicked - * event : 'hello', - * // set data-value property - * value : 'hello', - * hide : true - * }); - * } - * - * }, - * - * events : { - * "hello" : function(layoutInfo, value) { - * // here is event code - * } - * } - * }); - * ``` - * ### Use a plugin in toolbar - * - * ``` - * $("#editor").summernote({ - * ... - * toolbar : [ - * // display hello plugin in toolbar - * ['group', [ 'hello' ]] - * ] - * ... - * }); - * ``` - * - * * @param {Object} plugin - * @param {Object} [plugin.buttons] define plugin button. for detail, see to Renderer.addButtonInfo - * @param {Object} [plugin.dialogs] define plugin dialog. for detail, see to Renderer.addDialogInfo - * @param {Object} [plugin.events] add event in $.summernote.pluginEvents - * @param {Object} [plugin.langs] update $.summernote.lang - * @param {Object} [plugin.options] update $.summernote.options */ $.summernote.addPlugin = function (plugin) { - - // save plugin list - $.summernote.plugins.push(plugin); - if (plugin.buttons) { $.each(plugin.buttons, function (name, button) { renderer.addButtonInfo(name, button); @@ -166,157 +65,107 @@ define([ } }; - /* - * extend $.fn + /** + * extend jquery fn */ $.fn.extend({ /** - * @method - * Initialize summernote + * initialize summernote * - create editor layout and attach Mouse and keyboard events. - * - * ``` - * $("#summernote").summernote( { options ..} ); - * ``` - * - * @member $.fn - * @param {Object|String} options reference to $.summernote.options - * @return {this} + * + * @param {Object} options + * @returns {this} */ - summernote: function () { - // check first argument's type - // - {String}: External API call {{module}}.{{method}} - // - {Object}: init options - var type = $.type(list.head(arguments)); - var isExternalAPICalled = type === 'string'; - var hasInitOptions = type === 'object'; - - // extend default options with custom user options - var options = hasInitOptions ? list.head(arguments) : {}; - + summernote: function (options) { + // extend default options options = $.extend({}, $.summernote.options, options); - options.icons = $.extend({}, $.summernote.options.icons, options.icons); // Include langInfo in options for later use, e.g. for image drag-n-drop // Setup language info with en-US as default options.langInfo = $.extend(true, {}, $.summernote.lang['en-US'], $.summernote.lang[options.lang]); - // override plugin options - if (!isExternalAPICalled && hasInitOptions) { - for (var i = 0, len = $.summernote.plugins.length; i < len; i++) { - var plugin = $.summernote.plugins[i]; - - if (options.plugin[plugin.name]) { - $.summernote.plugins[i] = $.extend(true, plugin, options.plugin[plugin.name]); - } - } - } - this.each(function (idx, holder) { var $holder = $(holder); - // if layout isn't created yet, createLayout and attach events - if (!renderer.hasNoteEditor($holder)) { - renderer.createLayout($holder, options); - - var layoutInfo = renderer.layoutInfoFromHolder($holder); - $holder.data('layoutInfo', layoutInfo); + // createLayout with options + renderer.createLayout($holder, options); - eventHandler.attach(layoutInfo, options); - eventHandler.attachCustomEvent(layoutInfo, options); + var info = renderer.layoutInfoFromHolder($holder); + eventHandler.attach(info, options); + + // Textarea: auto filling the code before form submit. + if (dom.isTextarea($holder[0])) { + $holder.closest('form').submit(function () { + var contents = $holder.code(); + $holder.val(contents); + + // callback on submit + if (options.onsubmit) { + options.onsubmit(contents); + } + }); } }); - var $first = this.first(); - if ($first.length) { - var layoutInfo = renderer.layoutInfoFromHolder($first); - - // external API - if (isExternalAPICalled) { - var moduleAndMethod = list.head(list.from(arguments)); - var args = list.tail(list.from(arguments)); + // focus on first editable element + if (this.first().length && options.focus) { + var info = renderer.layoutInfoFromHolder(this.first()); + info.editable.focus(); + } - // TODO now external API only works for editor - var params = [moduleAndMethod, layoutInfo.editable()].concat(args); - return eventHandler.invoke.apply(eventHandler, params); - } else if (options.focus) { - // focus on first editable element for initialize editor - layoutInfo.editable().focus(); - } + // callback on init + if (this.length && options.oninit) { + options.oninit(); } return this; }, + // /** - * @method - * * get the HTML contents of note or set the HTML contents of note. * - * * get contents - * ``` - * var content = $("#summernote").code(); - * ``` - * * set contents - * - * ``` - * $("#summernote").code(html); - * ``` - * - * @member $.fn - * @param {String} [html] - HTML contents(optional, set) - * @return {this|String} - context(set) or HTML contents of note(get). + * @param {String} [sHTML] - HTML contents(optional, set) + * @returns {this|String} - context(set) or HTML contents of note(get). */ - code: function (html) { + code: function (sHTML) { // get the HTML contents of note - if (html === undefined) { + if (sHTML === undefined) { var $holder = this.first(); - if (!$holder.length) { - return; - } - - var layoutInfo = renderer.layoutInfoFromHolder($holder); - var $editable = layoutInfo && layoutInfo.editable(); - - if ($editable && $editable.length) { - var isCodeview = eventHandler.invoke('codeview.isActivated', layoutInfo); - eventHandler.invoke('codeview.sync', layoutInfo); - return isCodeview ? layoutInfo.codable().val() : - layoutInfo.editable().html(); + if (!$holder.length) { return; } + var info = renderer.layoutInfoFromHolder($holder); + if (!!(info && info.editable)) { + var isCodeview = info.editor.hasClass('codeview'); + if (isCodeview && agent.hasCodeMirror) { + info.codable.data('cmEditor').save(); + } + return isCodeview ? info.codable.val() : info.editable.html(); } - return dom.value($holder); + return dom.isTextarea($holder[0]) ? $holder.val() : $holder.html(); } // set the HTML contents of note this.each(function (i, holder) { - var layoutInfo = renderer.layoutInfoFromHolder($(holder)); - var $editable = layoutInfo && layoutInfo.editable(); - if ($editable) { - $editable.html(html); - } + var info = renderer.layoutInfoFromHolder($(holder)); + if (info && info.editable) { info.editable.html(sHTML); } }); return this; }, /** - * @method - * * destroy Editor Layout and detach Key and Mouse Event * - * @member $.fn - * @return {this} + * @returns {this} */ destroy: function () { this.each(function (idx, holder) { var $holder = $(holder); - if (!renderer.hasNoteEditor($holder)) { - return; - } - var info = renderer.layoutInfoFromHolder($holder); - var options = info.editor().data('options'); + if (!info || !info.editable) { return; } + + var options = info.editor.data('options'); eventHandler.detach(info, options); renderer.removeLayout($holder, info, options); diff --git a/addons/web_editor/static/lib/summernote/src/less/summernote.less b/addons/web_editor/static/lib/summernote/src/less/summernote.less index 806d1860ed7..c9c538e940d 100644 --- a/addons/web_editor/static/lib/summernote/src/less/summernote.less +++ b/addons/web_editor/static/lib/summernote/src/less/summernote.less @@ -1,4 +1,4 @@ -/* @_import "elements.less"; ODOO: removed call directly */ +/* @_import "elements.less"; */ /* Theme Variables ------------------------------------------*/ @@ -10,7 +10,6 @@ .note-editor { border: 1px solid @border-color; position: relative; - /* overflow: hidden; ODOO: removed for embedded editor */ /* dropzone */ @dropzone-color: lightskyblue; @@ -18,7 +17,8 @@ .note-dropzone { position: absolute; display: none; - z-index: 100; + z-index: 1; + border: 2px dashed @dropzone-color; color: @dropzone-color; background-color: white; opacity: 0.95; @@ -33,6 +33,7 @@ } &.hover { + border: 2px dashed @dropzone-active-color; color: @dropzone-active-color; } } @@ -41,16 +42,9 @@ display: table; } - /* codeview mode */ - &.codeview { - .note-editing-area { - .note-editable { - display: none; - } - .note-codable { - display: block; - } - } + .note-toolbar { + background-color: @background-color; + border-bottom: 1px solid @border-color; } /* fullscreen mode */ @@ -68,43 +62,13 @@ } } - .note-editing-area { - position: relative; - overflow: hidden; - - /* editable */ + /* codeview mode */ + &.codeview { .note-editable { - background-color: #fff; - color: #000; - padding: 10px; - overflow: auto; - outline: none; - - &[contenteditable=true]:empty:not(:focus):before { - content:attr(data-placeholder); - } - &[contenteditable="false"] { - background-color: #e5e5e5; - } + display: none; } - - /* codeable */ .note-codable { - display: none; - width: 100%; - padding: 10px; - border: none; - box-shadow: none; - font-family: Menlo, Monaco, monospace, sans-serif; - font-size: 14px; - color: #ccc; - background-color: #222; - resize: none; - - /* override BS2 default style */ - .box-sizing(border-box); - .rounded(0); - margin-bottom: 0; + display: block; } } @@ -112,9 +76,9 @@ .note-statusbar { background-color: @background-color; .note-resizebar { - padding-top: 1px; height: 8px; width: 100%; + border-top: 1px solid @border-color; cursor: ns-resize; .note-icon-bar { width: 20px; @@ -123,16 +87,50 @@ } } } + + /* editable */ + .note-editable[contenteditable=true]:empty:not(:focus):before { + content:attr(data-placeholder); + color: #a9a9a9; + } + + .note-editable { + padding: 10px; + overflow: auto; + outline: none; + } + .note-editable[contenteditable="false"] { + background-color: #e5e5e5; + } + + /* codeable */ + .note-codable { + display: none; + width: 100%; + padding: 10px; + border: none; + box-shadow: none; + font-family: Menlo, Monaco, monospace, sans-serif; + font-size: 14px; + color: #ccc; + background-color: #222; + resize: none; + + /* override BS2 default style */ + .box-sizing(border-box); + .rounded(0); + margin-bottom: 0; + } } /* Air Mode Layout -------------------------------------------*/ + ------------------------------------------*/ .note-air-editor { outline: none; } /* Popover -------------------------------------------*/ + ------------------------------------------*/ .note-popover .popover { max-width: none; .popover-content { @@ -151,8 +149,8 @@ } /* Popover and Toolbar (Button container) -------------------------------------------*/ -.note-popover .popover .popover-content, .panel-heading.note-toolbar { + ------------------------------------------*/ +.note-popover .popover .popover-content, .note-toolbar { margin: 0; padding: 0 0 5px 5px; @@ -260,17 +258,15 @@ &::before { right: 9px; left: auto !important; } &::after { right: 10px; left: auto !important; } } + /* dropdown-menu for selectbox */ - &.note-check { - li a i { - color: deepskyblue; - visibility: hidden; - } - li a.checked i { - visibility: visible; - } + li a i { + color: deepskyblue; + visibility: hidden; + } + li a.checked i { + visibility: visible; } - } .note-fontsize-10 { @@ -296,7 +292,7 @@ } /* Dialog -------------------------------------------*/ + ------------------------------------------*/ .note-dialog { &>div { display: none; /* BS2's hide pacth. */ @@ -390,7 +386,7 @@ } /* Handle -------------------------------------------*/ + ------------------------------------------*/ .note-handle { /* control selection */ .note-control-selection { @@ -448,12 +444,6 @@ cursor: se-resize; } - .note-control-se.note-control-holder { - cursor: default; - border-top: none; - border-left: none; - } - .note-control-selection-info { right: 0; bottom: 0; |
