diff options
| author | Nicolas Lempereur <nle@odoo.com> | 2015-08-28 17:22:17 +0200 |
|---|---|---|
| committer | Nicolas Lempereur <nle@odoo.com> | 2015-09-29 14:47:24 +0200 |
| commit | d33086c03fc1a0882be38ae62989b7f72df20d3b (patch) | |
| tree | 20b5776f6902916f4c67d9d356e9db2a107eb952 /addons/web_editor/static/lib/summernote/src/js/editing | |
| parent | e9ac1bff8bcef56ae09bbba3910202075f7eb89a (diff) | |
[IMP] web_editor: update summernote up to 2015-08-31
update summernote library up to revision:
https://github.com/summernote/summernote/commit/a0f26e300
see https://github.com/summernote/summernote/ for history
closes #8704
Diffstat (limited to 'addons/web_editor/static/lib/summernote/src/js/editing')
6 files changed, 626 insertions, 117 deletions
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 d0b0f2e9169..6a115edaa90 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,9 +5,17 @@ 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 () { @@ -15,7 +23,10 @@ define([ }; /** + * @method insertUnorderedList + * * toggle unordered list + * * @type command */ this.insertUnorderedList = function () { @@ -23,7 +34,10 @@ define([ }; /** + * @method indent + * * indent + * * @type command */ this.indent = function () { @@ -50,7 +64,10 @@ define([ }; /** + * @method outdent + * * outdent + * * @type command */ this.outdent = function () { @@ -78,7 +95,10 @@ define([ }; /** + * @method toggleList + * * toggle list + * * @param {String} listName - OL or UL */ this.toggleList = function (listName) { @@ -86,13 +106,16 @@ 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) { - self.wrapList(paras, listName); + wrappedParas = wrappedParas.concat(self.wrapList(paras, listName)); }); + paras = wrappedParas; // list to paragraph or change list style } else { var diffLists = rng.nodes(dom.isList, { @@ -106,16 +129,19 @@ define([ dom.replace(listNode, listName); }); } else { - this.releaseList(clustereds, true); + paras = this.releaseList(clustereds, true); } } - rng.select(); + range.createFromParaBookmark(bookmark, paras).select(); }; /** + * @method wrapList + * * @param {Node[]} paras * @param {String} listName + * @return {Node[]} */ this.wrapList = function (paras, listName) { var head = list.head(paras); @@ -127,7 +153,7 @@ define([ var listNode = prevList || dom.insertAfter(dom.create(listName || 'UL'), last); // P to LI - paras = $.map(paras, function (para) { + paras = paras.map(function (para) { return dom.isPurePara(para) ? dom.replace(para, 'LI') : para; }); @@ -138,9 +164,13 @@ define([ dom.appendChildNodes(listNode, list.from(nextList.childNodes)); dom.remove(nextList); } + + return paras; }; /** + * @method releaseList + * * @param {Array[]} clustereds * @param {Boolean} isEscapseToBody * @return {Node[]} @@ -157,19 +187,23 @@ define([ var lastList = headList.childNodes.length > 1 ? dom.splitTree(headList, { node: last.parentNode, offset: dom.position(last) + 1 - }, true) : null; + }, { + isSkipPaddingBlankHTML: true + }) : null; var middleList = dom.splitTree(headList, { node: head.parentNode, offset: dom.position(head) - }, true); + }, { + isSkipPaddingBlankHTML: 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 = $.map(paras, function (para) { + paras = paras.map(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 index 1acd957096d..732072b5d24 100644 --- a/addons/web_editor/static/lib/summernote/src/js/editing/Editor.js +++ b/addons/web_editor/static/lib/summernote/src/js/editing/Editor.js @@ -11,46 +11,70 @@ define([ 'summernote/editing/Bullet' ], function (agent, func, list, dom, range, async, Style, Typing, Table, Bullet) { + + var KEY_BOGUS = 'bogus'; + /** + * @class editing.Editor + * * Editor - * @class + * */ - var Editor = function () { + 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 change for overwrite - this.table = table; // odoo change for overwrite - this.typing = typing; // odoo change for overwrite - this.bullet = bullet; // odoo change for overwrite + 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) { - $editable.focus(); + 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 = []; @@ -61,6 +85,8 @@ define([ }; /** + * @method restoreRange + * * restore lately range * * @param {jQuery} $editable @@ -69,10 +95,17 @@ define([ var rng = $editable.data('range'); if (rng) { rng.select(); - $editable.focus(); + this.focus($editable); } }; + /** + * @method restoreNode + * + * restore lately node list + * + * @param {jQuery} $editable + */ this.restoreNode = function ($editable) { $editable.html(''); var child = $editable.data('childNodes'); @@ -80,106 +113,271 @@ define([ $editable[0].appendChild(child[index]); } }; + /** + * @method currentStyle + * * current style + * + * @param {Node} target + * @return {Object|Boolean} unfocus */ - this.currentStyle = function () { + this.currentStyle = function (target) { var rng = range.create(); - return rng ? rng.isOnEditable() && style.current(rng) : false; + var styleInfo = rng && rng.isOnEditable() ? style.current(rng.normalize()) : {}; + if (dom.isImg(target)) { + styleInfo.image = target; + } + return styleInfo; }; - var triggerOnChange = this.triggerOnChange = function ($editable) { - var onChange = $editable.data('callbacks').onChange; - if (onChange) { - onChange($editable.html(), $editable); - } + /** + * 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) { + var afterCommand = this.afterCommand = function ($editable, isPreventTrigger) { $editable.data('NoteHistory').recordUndo(); - triggerOnChange($editable); + 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', 'insertHorizontalRule', 'fontName']; + '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); + 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 = range.create(); + 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 () { - var rng = range.create(); + 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); }; @@ -188,6 +386,7 @@ define([ * @param {jQuery} $editable */ this.insertUnorderedList = function ($editable) { + beforeCommand($editable); bullet.insertUnorderedList($editable); afterCommand($editable); }; @@ -196,6 +395,7 @@ define([ * @param {jQuery} $editable */ this.indent = function ($editable) { + beforeCommand($editable); bullet.indent($editable); afterCommand($editable); }; @@ -204,6 +404,7 @@ define([ * @param {jQuery} $editable */ this.outdent = function ($editable) { + beforeCommand($editable); bullet.outdent($editable); afterCommand($editable); }; @@ -216,28 +417,32 @@ define([ */ 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 callbacks = $editable.data('callbacks'); - if (callbacks.onImageUploadError) { - callbacks.onImageUploadError(); - } + 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 - * @param {Boolean} [isInline] */ - this.insertNode = function ($editable, node, isInline) { - range.create().insertNode(node, isInline); + this.insertNode = function ($editable, node) { + beforeCommand($editable); + range.create().insertNode(node); + range.createFromNodeAfter(node).select(); afterCommand($editable); }; @@ -247,24 +452,40 @@ define([ * @param {String} text */ this.insertText = function ($editable, text) { - var textNode = this.createRange($editable).insertNode(dom.createText(text), true); + 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); }; @@ -280,33 +501,84 @@ define([ /* jshint ignore:end */ /** - * fontsize - * FIXME: Still buggy + * fontSize * * @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'); + 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 { - // 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'); + 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 }); @@ -321,21 +593,20 @@ define([ * @param {jQuery} $editable */ this.unlink = function ($editable) { - var rng = range.create(); + var rng = this.createRange($editable); if (rng.isOnAnchor()) { var anchor = dom.ancestor(rng.sc, dom.isAnchor); rng = range.createFromNode(anchor); rng.select(); - document.execCommand('unlink'); + beforeCommand($editable); + document.execCommand('unlink'); afterCommand($editable); } }; /** - * create link - * - * @type command + * create link (command) * * @param {jQuery} $editable * @param {Object} linkInfo @@ -344,28 +615,54 @@ define([ 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; + 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); } - 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); + 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 + }); } - $(anchor).html(linkText).attr({ - 'href': linkUrl, - 'target': isNewWindow ? '_blank' : null, - 'class': className + + $.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'); + } }); - range.createFromNode(anchor).select(); + 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); }; @@ -373,9 +670,13 @@ define([ * 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) { - $editable.focus(); + this.focus($editable); var rng = range.create().expand(dom.isAnchor); @@ -385,50 +686,76 @@ define([ return { range: rng, text: rng.toString(), - isNewWindow: $anchor.length ? $anchor.attr('target') === '_blank' : true, + 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'); - var rng = range.create(); - rng = rng.deleteContents(); + beforeCommand($editable); + + var rng = range.create().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; - } - - /** + * float me + * * @param {jQuery} $editable * @param {String} value + * @param {jQuery} $target */ - this.floatMe = function ($editable, value) { - var $target = $( getImgTarget() ); + 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); }; - this.imageShape = function ($editable, value) { - var $target = $( getImgTarget() ); + /** + * 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) { @@ -442,9 +769,11 @@ define([ * resize overlay element * @param {jQuery} $editable * @param {String} value + * @param {jQuery} $target - target element */ - this.resize = function ($editable, value) { - var $target = $( getImgTarget() ); + this.resize = function ($editable, value, $target) { + beforeCommand($editable); + $target.css({ width: value * 100 + '%', height: '' @@ -482,13 +811,50 @@ define([ * * @param {jQuery} $editable * @param {String} value - dummy argument (for keep interface) + * @param {jQuery} $target - target element */ - this.removeMedia = function ($editable, value) { - var $target = $( getImgTarget() ); + 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/editing/History.js b/addons/web_editor/static/lib/summernote/src/js/editing/History.js index dc301bf957b..2cc946488c2 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,10 +1,12 @@ -define(['summernote/core/range', 'summernote/core/dom'], function (range, dom) { +define(['summernote/core/range', 'summernote/core/dom'], function (range, dom) { // ODOO: suggest upstream /** - * History - * @class + * @class editing.History + * + * Editor History + * */ var History = function ($editable) { - var stack = [], stackOffset = -1, hasUndo = false; + var stack = [], stackOffset = -1; var editable = $editable[0]; var makeSnapshot = function () { @@ -14,6 +16,7 @@ define(['summernote/core/range', 'summernote/core/dom'], function (range, dom) { return { contents: $editable.html(), bookmark: (rng && dom.ancestor(rng.sc, dom.isEditable) ? rng.bookmark(editable) : emptyBookmark) + // ODOO: suggest upstream added " && dom.ancestor(rng.sc, dom.isEditable) " }; }; @@ -26,21 +29,29 @@ define(['summernote/core/range', 'summernote/core/dom'], function (range, dom) { } }; + /** + * undo + */ this.undo = function () { - if (0 < stackOffset || (stack.length - 1 === stackOffset && hasUndo)) { - if (stack.length - 1 === stackOffset) { - this.recordUndo(); - } + // Create snap shot if not yet recorded + if ($editable.html() !== stack[stackOffset].contents) { + this.recordUndo(); + } + + if (0 < stackOffset) { stackOffset--; applySnapshot(stack[stackOffset]); - hasUndo = !!stackOffset; } }; + /* ODOO: to suggest upstream */ this.hasUndo = function () { - return 0 < stackOffset || hasUndo; + return 0 < stackOffset; }; + /** + * redo + */ this.redo = function () { if (stack.length - 1 > stackOffset) { stackOffset++; @@ -48,12 +59,17 @@ define(['summernote/core/range', 'summernote/core/dom'], function (range, dom) { } }; + /* ODOO: to suggest upstream */ this.hasRedo = function () { return stack.length - 1 > stackOffset; }; - var last; + var last; // ODOO: to suggest upstream (since we may have several editor) + /** + * recorded undo + */ this.recordUndo = function () { + // ODOO: method totally rewritten // 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") { @@ -79,13 +95,14 @@ define(['summernote/core/range', 'summernote/core/dom'], function (range, dom) { // Create new snapshot and push it to the end stack.push(makeSnapshot()); - return true; }; + /* ODOO: to suggest upstream */ this.splitNext = function () { last = false; }; + /* ODOO: to suggest upstream */ this.reset = function () { last = false; 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 835d575be1b..bf749cd817b 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,20 +1,28 @@ define([ 'summernote/core/agent', + 'summernote/core/func', + 'summernote/core/list', 'summernote/core/dom' -], function (agent, dom) { +], function (agent, func, list, 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. - * @returns {Object} + * @return {Object} */ var jQueryCSS = function ($obj, propertyNames) { if (agent.jqueryVersion < 1.9) { @@ -28,6 +36,19 @@ 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 @@ -42,6 +63,58 @@ 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 @@ -49,10 +122,7 @@ define([ */ this.current = function (rng) { var $cont = $(dom.isText(rng.sc) ? rng.sc.parentNode : rng.sc); - 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); + var styleInfo = this.fromNode($cont); // document.queryCommandState for toggle state styleInfo['font-bold'] = document.queryCommandState('bold') ? 'bold' : 'normal'; @@ -79,7 +149,6 @@ 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 291f85128e5..634d5a79d7e 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,8 +2,10 @@ 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 2a094cafa22..f95eba9e9f3 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,12 +1,24 @@ define([ 'summernote/core/dom', - 'summernote/core/range' -], function (dom, range) { + 'summernote/core/range', + 'summernote/editing/Bullet' +], function (dom, range, Bullet) { + /** + * @class editing.Typing + * + * Typing + * + */ var Typing = function () { + // a Bullet instance to toggle lists off + var bullet = new Bullet(); + /** - * @param {jQuery} $editable + * insert tab + * + * @param {jQuery} $editable * @param {WrappedRange} rng * @param {Number} tabsize */ @@ -37,14 +49,22 @@ define([ var nextPara; // on paragraph: split paragraph if (splitRoot) { - nextPara = dom.splitTree(splitRoot, rng.getStartPoint()); + // 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()); - 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]; @@ -57,6 +77,7 @@ define([ } range.create(nextPara, 0).normalize().select(); + }; }; |
