summaryrefslogtreecommitdiff
path: root/addons/web_editor/static/lib/summernote/src/js/editing
diff options
context:
space:
mode:
authorNicolas Lempereur <nle@odoo.com>2015-08-28 17:22:17 +0200
committerNicolas Lempereur <nle@odoo.com>2015-09-29 14:47:24 +0200
commitd33086c03fc1a0882be38ae62989b7f72df20d3b (patch)
tree20b5776f6902916f4c67d9d356e9db2a107eb952 /addons/web_editor/static/lib/summernote/src/js/editing
parente9ac1bff8bcef56ae09bbba3910202075f7eb89a (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')
-rw-r--r--addons/web_editor/static/lib/summernote/src/js/editing/Bullet.js48
-rw-r--r--addons/web_editor/static/lib/summernote/src/js/editing/Editor.js526
-rw-r--r--addons/web_editor/static/lib/summernote/src/js/editing/History.js41
-rw-r--r--addons/web_editor/static/lib/summernote/src/js/editing/Style.js85
-rw-r--r--addons/web_editor/static/lib/summernote/src/js/editing/Table.js4
-rw-r--r--addons/web_editor/static/lib/summernote/src/js/editing/Typing.js39
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();
+
};
};