2423 lines
85 KiB
JavaScript
2423 lines
85 KiB
JavaScript
flectra.define('web_editor.summernote', function (require) {
|
|
'use strict';
|
|
|
|
var core = require('web.core');
|
|
require('summernote/summernote'); // wait that summernote is loaded
|
|
|
|
var _t = core._t;
|
|
|
|
// Summernote Lib (neek hack to make accessible: method and object)
|
|
// var agent = $.summernote.core.agent;
|
|
var dom = $.summernote.core.dom;
|
|
var range = $.summernote.core.range;
|
|
var list = $.summernote.core.list;
|
|
var key = $.summernote.core.key;
|
|
var eventHandler = $.summernote.eventHandler;
|
|
var editor = eventHandler.modules.editor;
|
|
var renderer = $.summernote.renderer;
|
|
var options = $.summernote.options;
|
|
|
|
// Browser-unify execCommand
|
|
var oldJustify = {};
|
|
_.each(['Left', 'Right', 'Full', 'Center'], function (align) {
|
|
oldJustify[align] = editor['justify' + align];
|
|
editor['justify' + align] = function ($editable, value) {
|
|
// Before calling the standard function, check all elements which have
|
|
// an 'align' attribute and mark them with their value
|
|
var $align = $editable.find('[align]');
|
|
_.each($align, function (el) {
|
|
var $el = $(el);
|
|
$el.data('__align', $el.attr('align'));
|
|
});
|
|
|
|
// Call the standard function
|
|
oldJustify[align].apply(this, arguments);
|
|
|
|
// Then:
|
|
|
|
// Remove the text-align of elements which lost the 'align' attribute
|
|
var $newAlign = $editable.find('[align]');
|
|
$align.not($newAlign).css('text-align', '');
|
|
|
|
// Transform the 'align' attribute into the 'text-align' css
|
|
// property for elements which received the 'align' attribute or whose
|
|
// 'align' attribute changed
|
|
_.each($newAlign, function (el) {
|
|
var $el = $(el);
|
|
|
|
var oldAlignValue = $align.data('__align');
|
|
var alignValue = $el.attr('align');
|
|
if (oldAlignValue === alignValue) {
|
|
// If the element already had an 'align' attribute and that it
|
|
// did not changed, do nothing (compatibility)
|
|
return;
|
|
}
|
|
|
|
$el.removeAttr('align');
|
|
$el.css('text-align', alignValue);
|
|
|
|
// Note the first step (removing the text-align of elemnts which
|
|
// lost the 'align' attribute) is kinda the same as this one, but
|
|
// this one handles the elements which have been edited with chrome
|
|
// or with this new system
|
|
$el.find('*').css('text-align', '');
|
|
});
|
|
|
|
// Unmark the elements
|
|
$align.removeData('__align');
|
|
};
|
|
});
|
|
|
|
|
|
// Add methods to summernote
|
|
|
|
dom.hasContentAfter = function (node) {
|
|
var next;
|
|
if (dom.isEditable(node)) return;
|
|
while (node.nextSibling) {
|
|
next = node.nextSibling;
|
|
if (next.tagName || dom.isVisibleText(next) || dom.isBR(next)) return next;
|
|
node = next;
|
|
}
|
|
};
|
|
dom.hasContentBefore = function (node) {
|
|
var prev;
|
|
if (dom.isEditable(node)) return;
|
|
while (node.previousSibling) {
|
|
prev = node.previousSibling;
|
|
if (prev.tagName || dom.isVisibleText(prev) || dom.isBR(prev)) return prev;
|
|
node = prev;
|
|
}
|
|
};
|
|
dom.ancestorHaveNextSibling = function (node, pred) {
|
|
pred = pred || dom.hasContentAfter;
|
|
while (!dom.isEditable(node) && (!node.nextSibling || !pred(node))) { node = node.parentNode; }
|
|
return node;
|
|
};
|
|
dom.ancestorHavePreviousSibling = function (node, pred) {
|
|
pred = pred || dom.hasContentBefore;
|
|
while (!dom.isEditable(node) && (!node.previousSibling || !pred(node))) { node = node.parentNode; }
|
|
return node;
|
|
};
|
|
dom.nextElementSibling = function (node) {
|
|
while (node) {
|
|
node = node.nextSibling;
|
|
if (node && node.tagName) {
|
|
break;
|
|
}
|
|
}
|
|
return node;
|
|
};
|
|
dom.previousElementSibling = function (node) {
|
|
while (node) {
|
|
node = node.previousSibling;
|
|
if (node && node.tagName) {
|
|
break;
|
|
}
|
|
}
|
|
return node;
|
|
};
|
|
dom.lastChild = function (node) {
|
|
while (node.lastChild) { node = node.lastChild; }
|
|
return node;
|
|
};
|
|
dom.firstChild = function (node) {
|
|
while (node.firstChild) { node = node.firstChild; }
|
|
return node;
|
|
};
|
|
dom.lastElementChild = function (node, deep) {
|
|
node = deep ? dom.lastChild(node) : node.lastChild;
|
|
return !node || node.tagName ? node : dom.previousElementSibling(node);
|
|
};
|
|
dom.firstElementChild = function (node, deep) {
|
|
node = deep ? dom.firstChild(node) : node.firstChild;
|
|
return !node || node.tagName ? node : dom.nextElementSibling(node);
|
|
};
|
|
dom.isEqual = function (prev, cur) {
|
|
if (prev.tagName !== cur.tagName) {
|
|
return false;
|
|
}
|
|
if ((prev.attributes ? prev.attributes.length : 0) !== (cur.attributes ? cur.attributes.length : 0)) {
|
|
return false;
|
|
}
|
|
|
|
function strip(text) {
|
|
return text && text.replace(/^\s+|\s+$/g, '').replace(/\s+/g, ' ');
|
|
}
|
|
var att, att2;
|
|
loop_prev:
|
|
for (var a in prev.attributes) {
|
|
att = prev.attributes[a];
|
|
for (var b in cur.attributes) {
|
|
att2 = cur.attributes[b];
|
|
if (att.name === att2.name) {
|
|
if (strip(att.value) !== strip(att2.value)) return false;
|
|
continue loop_prev;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
dom.hasOnlyStyle = function (node) {
|
|
for (var i = 0; i < node.attributes.length; i++) {
|
|
var attr = node.attributes[i];
|
|
if (attr.attributeName !== 'style') {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
dom.hasProgrammaticStyle = function (node) {
|
|
var styles = ["float", "display", "position", "top", "left", "right", "bottom"];
|
|
for (var i = 0; i < node.style.length; i++) {
|
|
var style = node.style[i];
|
|
if (styles.indexOf(style) !== -1) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
dom.mergeFilter = function (prev, cur, parent) {
|
|
// merge text nodes
|
|
if (prev && (dom.isText(prev) || (['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'LI', 'P'].indexOf(prev.tagName) !== -1 && prev !== cur.parentNode)) && dom.isText(cur)) {
|
|
return true;
|
|
}
|
|
if (prev && prev.tagName === "P" && dom.isText(cur)) {
|
|
return true;
|
|
}
|
|
if (prev && dom.isText(cur) && !dom.isVisibleText(cur) && (dom.isText(prev) || dom.isVisibleText(prev))) {
|
|
return true;
|
|
}
|
|
if (prev && !dom.isBR(prev) && dom.isEqual(prev, cur) &&
|
|
((prev.tagName && dom.getComputedStyle(prev).display === "inline" &&
|
|
cur.tagName && dom.getComputedStyle(cur).display === "inline"))) {
|
|
return true;
|
|
}
|
|
if (dom.isEqual(parent, cur) &&
|
|
((parent.tagName && dom.getComputedStyle(parent).display === "inline" &&
|
|
cur.tagName && dom.getComputedStyle(cur).display === "inline"))) {
|
|
return true;
|
|
}
|
|
if (parent && cur.tagName === "FONT" && (!cur.firstChild || (!cur.attributes.getNamedItem('style') && !cur.className.length))) {
|
|
return true;
|
|
}
|
|
// On backspace, webkit browsers create a <span> with a bunch of
|
|
// inline styles "remembering" where they come from.
|
|
// chances are we had e.g.
|
|
// <p>foo</p>
|
|
// <p>bar</p>
|
|
// merged the lines getting this in webkit
|
|
// <p>foo<span>bar</span></p>
|
|
if (parent && cur.tagName === "SPAN" && dom.hasOnlyStyle(cur) && !dom.hasProgrammaticStyle(cur)) {
|
|
return true;
|
|
}
|
|
};
|
|
dom.doMerge = function (prev, cur) {
|
|
if (prev.tagName) {
|
|
if (prev.childNodes.length && !prev.textContent.match(/\S/) && dom.firstElementChild(prev) && dom.isBR(dom.firstElementChild(prev))) {
|
|
prev.removeChild(dom.firstElementChild(prev));
|
|
}
|
|
if (cur.tagName) {
|
|
while (cur.firstChild) {
|
|
prev.appendChild(cur.firstChild);
|
|
}
|
|
cur.parentNode.removeChild(cur);
|
|
} else {
|
|
prev.appendChild(cur);
|
|
}
|
|
} else {
|
|
if (cur.tagName) {
|
|
var deep = cur;
|
|
while (deep.tagName && deep.firstChild) {deep = deep.firstChild;}
|
|
prev.appendData(deep.textContent);
|
|
cur.parentNode.removeChild(cur);
|
|
} else {
|
|
prev.appendData(cur.textContent);
|
|
cur.parentNode.removeChild(cur);
|
|
}
|
|
}
|
|
};
|
|
dom.merge = function (node, begin, so, end, eo, mergeFilter, all) {
|
|
mergeFilter = mergeFilter || dom.mergeFilter;
|
|
var _merged = false;
|
|
var add = all || false;
|
|
|
|
if (!begin) {
|
|
begin = node;
|
|
while (begin.firstChild) {begin = begin.firstChild;}
|
|
so = 0;
|
|
} else if (begin.tagName && begin.childNodes[so]) {
|
|
begin = begin.childNodes[so];
|
|
so = 0;
|
|
}
|
|
if (!end) {
|
|
end = node;
|
|
while (end.lastChild) {end = end.lastChild;}
|
|
eo = end.textContent.length-1;
|
|
} else if (end.tagName && end.childNodes[so]) {
|
|
end = end.childNodes[so];
|
|
so = 0;
|
|
}
|
|
|
|
begin = dom.firstChild(begin);
|
|
if (dom.isText(begin) && so > begin.textContent.length) {
|
|
so = 0;
|
|
}
|
|
end = dom.firstChild(end);
|
|
if (dom.isText(end) && eo > end.textContent.length) {
|
|
eo = 0;
|
|
}
|
|
|
|
function __merge(node) {
|
|
var merged = false;
|
|
var prev;
|
|
for (var k=0; k<node.childNodes.length; k++) {
|
|
var cur = node.childNodes[k];
|
|
|
|
if (cur === begin) {
|
|
if (!all) add = true;
|
|
}
|
|
|
|
__merge(cur);
|
|
dom.orderClass(dom.node(cur));
|
|
|
|
if (!add || !cur) continue;
|
|
if (cur === end) {
|
|
if (!all) add = false;
|
|
}
|
|
|
|
// create the first prev value
|
|
if (!prev) {
|
|
if (mergeFilter.call(dom, prev, cur, node)) {
|
|
prev = prev || cur.previousSibling;
|
|
dom.moveTo(cur, cur.parentNode, cur);
|
|
k--;
|
|
} else {
|
|
prev = cur;
|
|
}
|
|
continue;
|
|
} else if (mergeFilter.call(dom, null, cur, node)) { // merge with parent
|
|
prev = prev || cur.previousSibling;
|
|
dom.moveTo(cur, cur.parentNode, cur);
|
|
k--;
|
|
continue;
|
|
}
|
|
|
|
// merge nodes
|
|
if (mergeFilter.call(dom, prev, cur, node)) {
|
|
var p = prev;
|
|
var c = cur;
|
|
// compute prev/end and offset
|
|
if (prev.tagName) {
|
|
if (cur.tagName) {
|
|
if (cur === begin) begin = prev;
|
|
if (cur === end) end = prev;
|
|
}
|
|
} else {
|
|
if (cur.tagName) {
|
|
var deep = cur;
|
|
while (deep.tagName && deep.lastChild) {deep = deep.lastChild;}
|
|
if (deep === begin) {
|
|
so += prev.textContent.length;
|
|
begin = prev;
|
|
}
|
|
if (deep === end) {
|
|
eo += prev.textContent.length;
|
|
end = prev;
|
|
}
|
|
} else {
|
|
// merge text nodes
|
|
if (cur === begin) {
|
|
so += prev.textContent.length;
|
|
begin = prev;
|
|
}
|
|
if (cur === end) {
|
|
eo += prev.textContent.length;
|
|
end = prev;
|
|
}
|
|
}
|
|
}
|
|
|
|
dom.doMerge(p, c);
|
|
|
|
merged = true;
|
|
k--;
|
|
continue;
|
|
}
|
|
|
|
prev = cur;
|
|
}
|
|
|
|
// an other loop to merge the new shibbing nodes
|
|
if (merged) {
|
|
_merged = true;
|
|
__merge(node);
|
|
}
|
|
}
|
|
if (node) {
|
|
__merge(node);
|
|
}
|
|
|
|
return {
|
|
merged: _merged,
|
|
sc: begin,
|
|
ec: end,
|
|
so: so,
|
|
eo: eo
|
|
};
|
|
};
|
|
dom.autoMerge = function (target, previous) {
|
|
var node = dom.lastChild(target);
|
|
var nodes = [];
|
|
var temp;
|
|
|
|
while (node) {
|
|
nodes.push(node);
|
|
temp = (previous ? dom.hasContentBefore(node) : dom.hasContentAfter(node));
|
|
if (temp) {
|
|
if (!dom.isText(node) && !dom.isMergable(node) && temp.tagName !== node.tagName) {
|
|
nodes = [];
|
|
}
|
|
break;
|
|
}
|
|
node = node.parentNode;
|
|
}
|
|
|
|
while (nodes.length) {
|
|
node = nodes.pop();
|
|
if (node && (temp = (previous ? dom.hasContentBefore(node) : dom.hasContentAfter(node))) &&
|
|
temp.tagName === node.tagName &&
|
|
!dom.isText(node) &&
|
|
dom.isMergable(node) &&
|
|
!dom.isNotBreakable(node) && !dom.isNotBreakable(previous ? dom.previousElementSibling(node) : dom.nextElementSibling(node))) {
|
|
|
|
if (previous) {
|
|
dom.doMerge(temp, node);
|
|
} else {
|
|
dom.doMerge(node, temp);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
dom.removeSpace = function (node, begin, so, end, eo) {
|
|
var removed = false;
|
|
var add = node === begin;
|
|
|
|
if (node === begin && begin === end && dom.isBR(node)) {
|
|
return {
|
|
removed: removed,
|
|
sc: begin,
|
|
ec: end,
|
|
so: so,
|
|
eo: eo
|
|
};
|
|
}
|
|
|
|
(function __remove_space(node) {
|
|
if (!node) return;
|
|
var t_begin, t_end;
|
|
for (var k=0; k<node.childNodes.length; k++) {
|
|
var cur = node.childNodes[k];
|
|
|
|
if (cur === begin) add = true;
|
|
|
|
if (cur.tagName && cur.tagName !== "SCRIPT" && cur.tagName !== "STYLE" && dom.getComputedStyle(cur).whiteSpace !== "pre") {
|
|
__remove_space(cur);
|
|
}
|
|
|
|
if (!add) continue;
|
|
if (cur === end) add = false;
|
|
|
|
// remove begin empty text node
|
|
if (node.childNodes.length > 1 && dom.isText(cur) && !dom.isVisibleText(cur)) {
|
|
removed = true;
|
|
if (cur === begin) {
|
|
t_begin = dom.hasContentBefore(dom.ancestorHavePreviousSibling(cur));
|
|
if (t_begin) {
|
|
so = 0;
|
|
begin = dom.lastChild(t_begin);
|
|
}
|
|
}
|
|
if (cur === end) {
|
|
t_end = dom.hasContentAfter(dom.ancestorHaveNextSibling(cur));
|
|
if (t_end) {
|
|
eo = 1;
|
|
end = dom.firstChild(t_end);
|
|
if (dom.isText(end)) {
|
|
eo = end.textContent.length;
|
|
}
|
|
}
|
|
}
|
|
cur.parentNode.removeChild(cur);
|
|
begin = dom.lastChild(begin);
|
|
end = dom.lastChild(end);
|
|
k--;
|
|
continue;
|
|
}
|
|
|
|
// convert HTML space
|
|
if (dom.isText(cur)) {
|
|
var text;
|
|
var temp;
|
|
var _temp;
|
|
var exp1 = /[\t\n\r ]+/g;
|
|
var exp2 = /(?!([ ]|\u00A0)|^)\u00A0(?!([ ]|\u00A0)|$)/g;
|
|
if (cur === begin) {
|
|
temp = cur.textContent.substr(0, so);
|
|
_temp = temp.replace(exp1, ' ').replace(exp2, ' ');
|
|
so -= temp.length - _temp.length;
|
|
}
|
|
if (cur === end) {
|
|
temp = cur.textContent.substr(0, eo);
|
|
_temp = temp.replace(exp1, ' ').replace(exp2, ' ');
|
|
eo -= temp.length - _temp.length;
|
|
}
|
|
text = cur.textContent.replace(exp1, ' ').replace(exp2, ' ');
|
|
removed = removed || cur.textContent.length !== text.length;
|
|
cur.textContent = text;
|
|
}
|
|
}
|
|
})(node);
|
|
|
|
return {
|
|
removed: removed,
|
|
sc: begin,
|
|
ec: end,
|
|
so: !dom.isBR(begin) && so > 0 ? so : 0,
|
|
eo: dom.isBR(end) ? 0 : eo
|
|
};
|
|
};
|
|
dom.removeBetween = function (sc, so, ec, eo, towrite) {
|
|
var text;
|
|
if (ec.tagName) {
|
|
if (ec.childNodes[eo]) {
|
|
ec = ec.childNodes[eo];
|
|
eo = 0;
|
|
} else {
|
|
ec = dom.lastChild(ec);
|
|
eo = dom.nodeLength(ec);
|
|
}
|
|
}
|
|
if (sc.tagName) {
|
|
sc = sc.childNodes[so] || dom.firstChild(ec);
|
|
so = 0;
|
|
if (!dom.hasContentBefore(sc) && towrite) {
|
|
sc.parentNode.insertBefore(document.createTextNode('\u00A0'), sc);
|
|
}
|
|
}
|
|
if (!eo && sc !== ec) {
|
|
ec = dom.lastChild(dom.hasContentBefore(dom.ancestorHavePreviousSibling(ec)) || ec);
|
|
eo = ec.textContent.length;
|
|
}
|
|
|
|
var ancestor = dom.commonAncestor(sc.tagName ? sc.parentNode : sc, ec.tagName ? ec.parentNode : ec) || dom.ancestor(sc, dom.isEditable);
|
|
|
|
if (!dom.isContentEditable(ancestor)) {
|
|
return {
|
|
sc: sc,
|
|
so: so,
|
|
ec: sc,
|
|
eo: eo
|
|
};
|
|
}
|
|
|
|
if (ancestor.tagName) {
|
|
var ancestor_sc = sc;
|
|
var ancestor_ec = ec;
|
|
while (ancestor !== ancestor_sc && ancestor !== ancestor_sc.parentNode) { ancestor_sc = ancestor_sc.parentNode; }
|
|
while (ancestor !== ancestor_ec && ancestor !== ancestor_ec.parentNode) { ancestor_ec = ancestor_ec.parentNode; }
|
|
|
|
|
|
var node = dom.node(sc);
|
|
if (!dom.isNotBreakable(node) && !dom.isVoid(sc)) {
|
|
sc = dom.splitTree(ancestor_sc, {'node': sc, 'offset': so});
|
|
}
|
|
var before = dom.hasContentBefore(dom.ancestorHavePreviousSibling(sc));
|
|
|
|
var after;
|
|
if (ec.textContent.slice(eo, Infinity).match(/\S|\u00A0/)) {
|
|
after = dom.splitTree(ancestor_ec, {'node': ec, 'offset': eo});
|
|
} else {
|
|
after = dom.hasContentAfter(dom.ancestorHaveNextSibling(ec));
|
|
}
|
|
|
|
var nodes = dom.listBetween(sc, ec);
|
|
|
|
var ancestor_first_last = function (node) {
|
|
return node === before || node === after;
|
|
};
|
|
|
|
for (var i=0; i<nodes.length; i++) {
|
|
if (!dom.ancestor(nodes[i], ancestor_first_last) && !$.contains(nodes[i], before) && !$.contains(nodes[i], after) && !dom.isEditable(nodes[i])) {
|
|
nodes[i].parentNode.removeChild(nodes[i]);
|
|
}
|
|
}
|
|
|
|
if (dom.listAncestor(after).length <= dom.listAncestor(before).length) {
|
|
sc = dom.lastChild(before || ancestor);
|
|
so = dom.nodeLength(sc);
|
|
} else {
|
|
sc = dom.firstChild(after);
|
|
so = 0;
|
|
}
|
|
|
|
if (dom.isVoid(node)) {
|
|
// we don't need to append a br
|
|
} else if (towrite && !node.firstChild && node.parentNode && !dom.isNotBreakable(node)) {
|
|
var br = $("<br/>")[0];
|
|
node.appendChild(sc);
|
|
sc = br;
|
|
so = 0;
|
|
} else if (!ancestor.children.length && !ancestor.textContent.match(/\S|\u00A0/)) {
|
|
sc = $("<br/>")[0];
|
|
so = 0;
|
|
$(ancestor).prepend(sc);
|
|
} else if (dom.isText(sc)) {
|
|
text = sc.textContent.replace(/[ \t\n\r]+$/, '\u00A0');
|
|
so = Math.min(so, text.length);
|
|
sc.textContent = text;
|
|
}
|
|
} else {
|
|
text = ancestor.textContent;
|
|
ancestor.textContent = text.slice(0, so) + text.slice(eo, Infinity).replace(/^[ \t\n\r]+/, '\u00A0');
|
|
}
|
|
|
|
eo = so;
|
|
if (!dom.isBR(sc) && !dom.isVisibleText(sc) && !dom.isText(dom.hasContentBefore(sc)) && !dom.isText(dom.hasContentAfter(sc))) {
|
|
ancestor = dom.node(sc);
|
|
text = document.createTextNode('\u00A0');
|
|
$(sc).before(text);
|
|
sc = text;
|
|
so = 0;
|
|
eo = 1;
|
|
}
|
|
|
|
var parentNode = sc && sc.parentNode;
|
|
if (parentNode && sc.tagName === 'BR') {
|
|
sc = parentNode;
|
|
ec = parentNode;
|
|
}
|
|
|
|
return {
|
|
sc: sc,
|
|
so: so,
|
|
ec: sc,
|
|
eo: eo
|
|
};
|
|
};
|
|
dom.indent = function (node) {
|
|
var style = dom.isCell(node) ? 'paddingLeft' : 'marginLeft';
|
|
var margin = parseFloat(node.style[style] || 0)+1.5;
|
|
node.style[style] = margin + "em";
|
|
return margin;
|
|
};
|
|
dom.outdent = function (node) {
|
|
var style = dom.isCell(node) ? 'paddingLeft' : 'marginLeft';
|
|
var margin = parseFloat(node.style[style] || 0)-1.5;
|
|
node.style[style] = margin > 0 ? margin + "em" : "";
|
|
return margin;
|
|
};
|
|
dom.scrollIntoViewIfNeeded = function (node) {
|
|
node = dom.node(node);
|
|
|
|
var $span;
|
|
if (dom.isBR(node)) {
|
|
$span = $('<span/>').text('\u00A0');
|
|
$(node).after($span);
|
|
node = $span[0];
|
|
}
|
|
|
|
if (node.scrollIntoViewIfNeeded) {
|
|
node.scrollIntoViewIfNeeded(false);
|
|
} else {
|
|
var offsetParent = node.offsetParent;
|
|
while (offsetParent) {
|
|
var elY = 0;
|
|
var elH = node.offsetHeight;
|
|
var parent = node;
|
|
|
|
while (offsetParent && parent) {
|
|
elY += node.offsetTop;
|
|
|
|
// get if a parent have a scrollbar
|
|
parent = node.parentNode;
|
|
while (parent !== offsetParent &&
|
|
(parent.tagName === "BODY" || ["auto", "scroll"].indexOf(dom.getComputedStyle(parent).overflowY) === -1)) {
|
|
parent = parent.parentNode;
|
|
}
|
|
node = parent;
|
|
|
|
if (parent !== offsetParent) {
|
|
elY -= parent.offsetTop;
|
|
parent = null;
|
|
}
|
|
|
|
offsetParent = node.offsetParent;
|
|
}
|
|
|
|
if ((node.tagName === "BODY" || ["auto", "scroll"].indexOf(dom.getComputedStyle(node).overflowY) !== -1) &&
|
|
(node.scrollTop + node.clientHeight) < (elY + elH)) {
|
|
node.scrollTop = (elY + elH) - node.clientHeight;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($span) {
|
|
$span.remove();
|
|
}
|
|
|
|
return;
|
|
};
|
|
dom.moveTo = function (node, target, before) {
|
|
var nodes = [];
|
|
while (node.firstChild) {
|
|
nodes.push(node.firstChild);
|
|
if (before) {
|
|
target.insertBefore(node.firstChild, before);
|
|
} else {
|
|
target.appendChild(node.firstChild);
|
|
}
|
|
}
|
|
node.parentNode.removeChild(node);
|
|
return nodes;
|
|
};
|
|
dom.isMergable = function (node) {
|
|
return node.tagName && "h1 h2 h3 h4 h5 h6 p b bold i u code sup strong small li a ul ol font".indexOf(node.tagName.toLowerCase()) !== -1;
|
|
};
|
|
dom.isSplitable = function (node) {
|
|
return node.tagName && "h1 h2 h3 h4 h5 h6 p b bold i u code sup strong small li a font".indexOf(node.tagName.toLowerCase()) !== -1;
|
|
};
|
|
dom.isRemovableEmptyNode = function (node) {
|
|
return "h1 h2 h3 h4 h5 h6 p b bold i u code sup strong small li a ul ol font span br".indexOf(node.tagName.toLowerCase()) !== -1;
|
|
};
|
|
dom.isForbiddenNode = function (node) {
|
|
return node.tagName === "BR" || $(node).is(".fa, img");
|
|
};
|
|
/**
|
|
* @todo 'so' and 'eo' were added as a bugfix and are not given everytime. They
|
|
* however should be as the function may be wrong without them (for example,
|
|
* when asking the list between an element and its parent, as there is no path
|
|
* from the beginning of the former to the beginning of the later).
|
|
*/
|
|
dom.listBetween = function (sc, ec, so, eo) {
|
|
var nodes = [];
|
|
var ancestor = dom.commonAncestor(sc, ec);
|
|
dom.walkPoint({'node': sc, 'offset': so || 0}, {'node': ec, 'offset': eo || 0}, function (point) {
|
|
if (ancestor !== point.node || ancestor === sc || ancestor === ec) {
|
|
nodes.push(point.node);
|
|
}
|
|
});
|
|
return list.unique(nodes);
|
|
};
|
|
dom.isNotBreakable = function (node) {
|
|
// avoid triple click => crappy dom
|
|
return !dom.isText(node) && !dom.isBR(dom.firstChild(node)) && dom.isVoid(dom.firstChild(node));
|
|
};
|
|
dom.isContentEditable = function (node) {
|
|
return $(node).closest('[contenteditable]').prop('contenteditable') === 'true';
|
|
};
|
|
dom.isContentEditableFalse = function (node) {
|
|
return $(node).closest('[contenteditable]').prop('contenteditable') === 'false';
|
|
};
|
|
dom.isFont = function (node) {
|
|
var nodeName = node && node.nodeName.toUpperCase();
|
|
return node && (nodeName === "FONT" ||
|
|
(nodeName === "SPAN" && (
|
|
node.className.match(/(^|\s)fa(\s|$)/i) ||
|
|
node.className.match(/(^|\s)(text|bg)-/i) ||
|
|
(node.attributes.style && node.attributes.style.value.match(/(^|\s)(color|background-color|font-size):/i)))) );
|
|
};
|
|
dom.isVisibleText = function (textNode) {
|
|
return !!textNode.textContent.match(/\S|\u00A0/);
|
|
};
|
|
var old_isVisiblePoint = dom.isVisiblePoint;
|
|
dom.isVisiblePoint = function (point) {
|
|
return point.node.nodeType !== 8 && old_isVisiblePoint.apply(this, arguments);
|
|
};
|
|
dom.orderStyle = function (node) {
|
|
var style = node.getAttribute('style');
|
|
if (!style) return null;
|
|
style = style.replace(/[\s\n\r]+/, ' ').replace(/^ ?;? ?| ?;? ?$/g, '').replace(/ ?; ?/g, ';');
|
|
if (!style.length) {
|
|
node.removeAttribute("style");
|
|
return null;
|
|
}
|
|
style = style.split(";");
|
|
style.sort();
|
|
style = style.join("; ")+";";
|
|
node.setAttribute('style', style);
|
|
return style;
|
|
};
|
|
dom.orderClass = function (node) {
|
|
var className = node.getAttribute && node.getAttribute('class');
|
|
if (!className) return null;
|
|
className = className.replace(/[\s\n\r]+/, ' ').replace(/^ | $/g, '').replace(/ +/g, ' ');
|
|
if (!className.length) {
|
|
node.removeAttribute("class");
|
|
return null;
|
|
}
|
|
className = className.split(" ");
|
|
className.sort();
|
|
className = className.join(" ");
|
|
node.setAttribute('class', className);
|
|
return className;
|
|
};
|
|
dom.node = function (node) {
|
|
return dom.isText(node) ? node.parentNode : node;
|
|
};
|
|
dom.moveContent = function (from, to) {
|
|
if (from === to) {
|
|
return;
|
|
}
|
|
if (from.parentNode === to) {
|
|
while (from.lastChild) {
|
|
dom.insertAfter(from.lastChild, from);
|
|
}
|
|
} else {
|
|
while (from.firstChild && from.firstChild !== to) {
|
|
to.appendChild(from.firstChild);
|
|
}
|
|
}
|
|
};
|
|
dom.getComputedStyle = function (node) {
|
|
return node.nodeType === Node.COMMENT_NODE ? {} : window.getComputedStyle(node);
|
|
};
|
|
|
|
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
|
|
range.WrappedRange.prototype.reRange = function (keep_end, isNotBreakable) {
|
|
var sc = this.sc;
|
|
var so = this.so;
|
|
var ec = this.ec;
|
|
var eo = this.eo;
|
|
isNotBreakable = isNotBreakable || dom.isNotBreakable;
|
|
|
|
// search the first snippet editable node
|
|
var start = keep_end ? ec : sc;
|
|
while (start) {
|
|
if (isNotBreakable(start, sc, so, ec, eo)) {
|
|
break;
|
|
}
|
|
start = start.parentNode;
|
|
}
|
|
|
|
// check if the end caret have the same node
|
|
var lastFilterEnd;
|
|
var end = keep_end ? sc : ec;
|
|
while (end) {
|
|
if (start === end) {
|
|
break;
|
|
}
|
|
if (isNotBreakable(end, sc, so, ec, eo)) {
|
|
lastFilterEnd = end;
|
|
}
|
|
end = end.parentNode;
|
|
}
|
|
if (lastFilterEnd) {
|
|
end = lastFilterEnd;
|
|
}
|
|
if (!end) {
|
|
end = document.getElementsByTagName('body')[0];
|
|
}
|
|
|
|
// if same node, keep range
|
|
if (start === end || !start) {
|
|
return this;
|
|
}
|
|
|
|
// reduce or extend the range to don't break a isNotBreakable area
|
|
if ($.contains(start, end)) {
|
|
|
|
if (keep_end) {
|
|
sc = dom.lastChild(dom.hasContentBefore(dom.ancestorHavePreviousSibling(end)) || sc);
|
|
so = sc.textContent.length;
|
|
} else if (!eo) {
|
|
ec = dom.lastChild(dom.hasContentBefore(dom.ancestorHavePreviousSibling(end)) || ec);
|
|
eo = ec.textContent.length;
|
|
} else {
|
|
ec = dom.firstChild(dom.hasContentAfter(dom.ancestorHaveNextSibling(end)) || ec);
|
|
eo = 0;
|
|
}
|
|
} else {
|
|
|
|
if (keep_end) {
|
|
sc = dom.firstChild(start);
|
|
so = 0;
|
|
} else {
|
|
ec = dom.lastChild(start);
|
|
eo = ec.textContent.length;
|
|
}
|
|
}
|
|
|
|
return new range.WrappedRange(sc, so, ec, eo);
|
|
};
|
|
/**
|
|
* Returns the image the range is in or matches (if any, false otherwise).
|
|
*
|
|
* @todo this implementation may not cover all corner cases but should do the
|
|
* trick for all reproductible ones
|
|
* @returns {DOMElement|boolean}
|
|
*/
|
|
range.WrappedRange.prototype.isOnImg = function () {
|
|
// If not a selection but a cursor position, just check if a point's
|
|
// ancestor is an image or not
|
|
if (this.sc === this.ec && this.so === this.eo) {
|
|
return dom.ancestor(this.sc, dom.isImg);
|
|
}
|
|
|
|
var startPoint = {node: this.sc, offset: this.so};
|
|
var endPoint = {node: this.ec, offset: this.eo};
|
|
|
|
var nb = 0;
|
|
var image;
|
|
var textNode;
|
|
dom.walkPoint(startPoint, endPoint, function (point) {
|
|
// If the element has children (not a text node and not empty node),
|
|
// the element cannot be considered as selected (these children will
|
|
// be processed to determine that)
|
|
if (dom.hasChildren(point.node)) {
|
|
return;
|
|
}
|
|
|
|
// Check if an ancestor of the current point is an image
|
|
var pointImg = dom.ancestor(point.node, dom.isImg);
|
|
var isText = dom.isText(point.node);
|
|
|
|
// Check if a visible element is selected, i.e.
|
|
// - If an ancestor of the current is an image we did not see yet
|
|
// - If the point is not in a br or a text (so a node with no children)
|
|
// - If the point is in a non empty text node we already saw
|
|
if (pointImg ?
|
|
(image !== pointImg) :
|
|
((!dom.isBR(point.node) && !isText) || (textNode === point.node && point.node.textContent.match(/\S|\u00A0/)))) {
|
|
nb++;
|
|
}
|
|
|
|
// If an ancestor of the current point is an image, then save it as the
|
|
// image we are looking for
|
|
if (pointImg) {
|
|
image = pointImg;
|
|
}
|
|
// If the current point is a text node save it as the last text node
|
|
// seen (if we see it again, this might mean it is selected)
|
|
if (isText) {
|
|
textNode = point.node;
|
|
}
|
|
});
|
|
|
|
return nb === 1 && image;
|
|
};
|
|
range.WrappedRange.prototype.deleteContents = function (towrite) {
|
|
if (this.sc === this.ec && this.so === this.eo) {
|
|
return this;
|
|
}
|
|
|
|
var r;
|
|
var image = this.isOnImg();
|
|
if (image) {
|
|
// If the range matches/is in an image, then the image is to be removed
|
|
// and the cursor moved to its previous position
|
|
var parentNode = image.parentNode;
|
|
var index = _.indexOf(parentNode.childNodes, image);
|
|
parentNode.removeChild(image);
|
|
r = new range.WrappedRange(parentNode, index, parentNode, index);
|
|
} else {
|
|
r = dom.removeBetween(this.sc, this.so, this.ec, this.eo, towrite);
|
|
}
|
|
|
|
$(dom.node(r.sc)).trigger("click"); // trigger click to disable and reanable editor and image handler
|
|
return new range.WrappedRange(r.sc, r.so, r.ec, r.eo);
|
|
};
|
|
range.WrappedRange.prototype.clean = function (mergeFilter, all) {
|
|
var node = dom.node(this.sc === this.ec ? this.sc : this.commonAncestor());
|
|
node = node || $(this.sc).closest('[contenteditable]')[0];
|
|
if (node.childNodes.length <=1) {
|
|
return this;
|
|
}
|
|
|
|
var merge = dom.merge(node, this.sc, this.so, this.ec, this.eo, mergeFilter, all);
|
|
var rem = dom.removeSpace(node.parentNode, merge.sc, merge.so, merge.ec, merge.eo);
|
|
|
|
if (merge.merged || rem.removed) {
|
|
return range.create(rem.sc, rem.so, rem.ec, rem.eo);
|
|
}
|
|
return this;
|
|
};
|
|
range.WrappedRange.prototype.remove = function (mergeFilter) {
|
|
};
|
|
range.WrappedRange.prototype.isOnCellFirst = function () {
|
|
var node = dom.ancestor(this.sc, function (node) {return ["LI", "DIV", "TD","TH"].indexOf(node.tagName) !== -1;});
|
|
return node && ["TD","TH"].indexOf(node.tagName) !== -1;
|
|
};
|
|
range.WrappedRange.prototype.isContentEditable = function () {
|
|
return dom.isContentEditable(this.sc) && (this.sc === this.ec || dom.isContentEditable(this.ec));
|
|
};
|
|
|
|
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
|
|
renderer.tplButtonInfo.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="fa fa-check"></i> ' + v +
|
|
'</a></li>';
|
|
}, '');
|
|
|
|
var sLabel = '<span class="note-current-fontsize">11</span>';
|
|
return renderer.getTemplate().button(sLabel, {
|
|
title: lang.font.size,
|
|
dropdown: '<ul class="dropdown-menu">' + items + '</ul>'
|
|
});
|
|
};
|
|
|
|
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
|
|
key.nameFromCode[46] = 'DELETE';
|
|
key.nameFromCode[27] = 'ESCAPE';
|
|
|
|
options.keyMap.pc['BACKSPACE'] = 'backspace';
|
|
options.keyMap.pc['DELETE'] = 'delete';
|
|
options.keyMap.pc['ENTER'] = 'enter';
|
|
options.keyMap.pc['ESCAPE'] = 'cancel';
|
|
options.keyMap.mac['SHIFT+TAB'] = 'untab';
|
|
options.keyMap.pc['UP'] = 'up';
|
|
options.keyMap.pc['DOWN'] = 'down';
|
|
|
|
options.keyMap.mac['BACKSPACE'] = 'backspace';
|
|
options.keyMap.mac['DELETE'] = 'delete';
|
|
options.keyMap.mac['ENTER'] = 'enter';
|
|
options.keyMap.mac['ESCAPE'] = 'cancel';
|
|
options.keyMap.mac['UP'] = 'up';
|
|
options.keyMap.mac['DOWN'] = 'down';
|
|
|
|
options.styleTags = ['p', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote'];
|
|
|
|
$.summernote.pluginEvents.insertTable = function (event, editor, layoutInfo, sDim) {
|
|
var $editable = layoutInfo.editable();
|
|
var dimension = sDim.split('x');
|
|
var r = range.create();
|
|
if (!r) return;
|
|
r = r.deleteContents(true);
|
|
|
|
var table = editor.table.createTable(dimension[0], dimension[1]);
|
|
var parent = r.sc;
|
|
while (dom.isText(parent.parentNode) || dom.isRemovableEmptyNode(parent.parentNode)) {
|
|
parent = parent.parentNode;
|
|
}
|
|
var node = dom.splitTree(parent, {'node': r.sc, 'offset': r.so}) || r.sc;
|
|
node.parentNode.insertBefore(table, node);
|
|
|
|
if ($(node).text() === '' || node.textContent === '\u00A0') {
|
|
node.parentNode.removeChild(node);
|
|
}
|
|
|
|
editor.afterCommand($editable);
|
|
event.preventDefault();
|
|
return false;
|
|
};
|
|
$.summernote.pluginEvents.tab = function (event, editor, layoutInfo, outdent) {
|
|
var $editable = layoutInfo.editable();
|
|
$editable.data('NoteHistory').recordUndo($editable, 'tab');
|
|
var r = range.create();
|
|
outdent = outdent || false;
|
|
event.preventDefault();
|
|
|
|
if (r && (dom.ancestor(r.sc, dom.isCell) || dom.ancestor(r.ec, dom.isCell))) {
|
|
if (r.isCollapsed() && r.isOnCell() && r.isOnCellFirst()) {
|
|
var td = dom.ancestor(r.sc, dom.isCell);
|
|
if (!outdent && !dom.nextElementSibling(td) && !dom.nextElementSibling(td.parentNode)) {
|
|
var last = dom.lastChild(td);
|
|
range.create(last, dom.nodeLength(last), last, dom.nodeLength(last)).select();
|
|
$.summernote.pluginEvents.enter(event, editor, layoutInfo);
|
|
} else if (outdent && !dom.previousElementSibling(td) && !$(td.parentNode).text().match(/\S/)) {
|
|
$.summernote.pluginEvents.backspace(event, editor, layoutInfo);
|
|
} else {
|
|
editor.table.tab(r, outdent);
|
|
}
|
|
} else {
|
|
$.summernote.pluginEvents.indent(event, editor, layoutInfo, outdent);
|
|
}
|
|
} else if (r && r.isCollapsed()) {
|
|
if (!r.sc.textContent.slice(0,r.so).match(/\S/) && r.isOnList()) {
|
|
if (outdent) {
|
|
$.summernote.pluginEvents.outdent(event, editor, layoutInfo);
|
|
} else {
|
|
$.summernote.pluginEvents.indent(event, editor, layoutInfo);
|
|
}
|
|
} else {
|
|
var next;
|
|
if (!outdent) {
|
|
if (dom.isText(r.sc)) {
|
|
next = r.sc.splitText(r.so);
|
|
} else {
|
|
next = document.createTextNode('');
|
|
$(r.sc.childNodes[r.so]).before(next);
|
|
}
|
|
editor.typing.insertTab($editable, r, options.tabsize);
|
|
r = range.create(next, 0, next, 0);
|
|
r = dom.merge(r.sc.parentNode, r.sc, r.so, r.ec, r.eo, null, true);
|
|
range.create(r.sc, r.so, r.ec, r.eo).select();
|
|
} else {
|
|
r = dom.merge(r.sc.parentNode, r.sc, r.so, r.ec, r.eo, null, true);
|
|
r = range.create(r.sc, r.so, r.ec, r.eo);
|
|
next = r.sc.splitText(r.so);
|
|
r.sc.textContent = r.sc.textContent.replace(/(\u00A0)+$/g, '');
|
|
next.textContent = next.textContent.replace(/^(\u00A0)+/g, '');
|
|
range.create(r.sc, r.sc.textContent.length, r.sc, r.sc.textContent.length).select();
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
$.summernote.pluginEvents.untab = function (event, editor, layoutInfo) {
|
|
return $.summernote.pluginEvents.tab(event, editor, layoutInfo, true);
|
|
};
|
|
$.summernote.pluginEvents.up = function (event, editor, layoutInfo) {
|
|
var r = range.create();
|
|
var node = dom.firstChild(r.sc.childNodes[r.so] || r.sc);
|
|
if (!r.isOnCell() || (!dom.isCell(node) && dom.hasContentBefore(node) && (!dom.isBR(dom.hasContentBefore(node)) || !dom.isText(node) || dom.isVisibleText(node) || dom.hasContentBefore(dom.hasContentBefore(node))))) {
|
|
return;
|
|
}
|
|
event.preventDefault();
|
|
var td = dom.ancestor(r.sc, dom.isCell);
|
|
var tr = td.parentNode;
|
|
var target = tr.previousElementSibling && tr.previousElementSibling.children[_.indexOf(tr.children, td)];
|
|
if (!target) {
|
|
target = (dom.ancestorHavePreviousSibling(tr) || tr).previousSibling;
|
|
}
|
|
if (target) {
|
|
range.create(dom.lastChild(target), dom.lastChild(target).textContent.length).select();
|
|
}
|
|
};
|
|
$.summernote.pluginEvents.down = function (event, editor, layoutInfo) {
|
|
var r = range.create();
|
|
var node = dom.firstChild(r.sc.childNodes[r.so] || r.sc);
|
|
if (!r.isOnCell() || (!dom.isCell(node) && dom.hasContentAfter(node) && (!dom.isBR(dom.hasContentAfter(node)) || !dom.isText(node) || dom.isVisibleText(node) || dom.hasContentAfter(dom.hasContentAfter(node))))) {
|
|
return;
|
|
}
|
|
event.preventDefault();
|
|
var td = dom.ancestor(r.sc, dom.isCell);
|
|
var tr = td.parentNode;
|
|
var target = tr.nextElementSibling && tr.nextElementSibling.children[_.indexOf(tr.children, td)];
|
|
if (!target) {
|
|
target = (dom.ancestorHaveNextSibling(tr) || tr).nextSibling;
|
|
}
|
|
if (target) {
|
|
range.create(dom.firstChild(target), 0).select();
|
|
}
|
|
};
|
|
$.summernote.pluginEvents.enter = function (event, editor, layoutInfo) {
|
|
var $editable = layoutInfo.editable();
|
|
$editable.data('NoteHistory').recordUndo($editable, 'enter');
|
|
|
|
var r = range.create();
|
|
if (!r.isContentEditable()) {
|
|
event.preventDefault();
|
|
return false;
|
|
}
|
|
if (!r.isCollapsed()) {
|
|
r = r.deleteContents();
|
|
r.select();
|
|
}
|
|
|
|
var br = $("<br/>")[0];
|
|
|
|
var node;
|
|
var $node;
|
|
var $clone;
|
|
var contentBefore = r.sc.textContent.slice(0,r.so).match(/\S|\u00A0/);
|
|
if (!contentBefore && dom.isText(r.sc)) {
|
|
node = r.sc.previousSibling;
|
|
while (!contentBefore && node && dom.isText(node)) {
|
|
contentBefore = dom.isVisibleText(node);
|
|
node = node.previousSibling;
|
|
}
|
|
}
|
|
|
|
node = dom.node(r.sc);
|
|
var exist = r.sc.childNodes[r.so] || r.sc;
|
|
exist = dom.isVisibleText(exist) || dom.isBR(exist) ? exist : dom.hasContentAfter(exist) || (dom.hasContentBefore(exist) || exist);
|
|
|
|
// table: add a tr
|
|
var td = dom.ancestor(node, dom.isCell);
|
|
if (td && !dom.nextElementSibling(node) && !dom.nextElementSibling(td) && !dom.nextElementSibling(td.parentNode) && (!dom.isText(r.sc) || !r.sc.textContent.slice(r.so).match(/\S|\u00A0/))) {
|
|
$node = $(td.parentNode);
|
|
$clone = $node.clone();
|
|
$clone.children().html(dom.blank);
|
|
$node.after($clone);
|
|
node = dom.firstElementChild($clone[0]) || $clone[0];
|
|
range.create(node, 0, node, 0).select();
|
|
dom.scrollIntoViewIfNeeded(br);
|
|
event.preventDefault();
|
|
return false;
|
|
}
|
|
|
|
var last = node;
|
|
while (node && dom.isSplitable(node) && !dom.isList(node)) {
|
|
last = node;
|
|
node = node.parentNode;
|
|
}
|
|
|
|
if (last === node && !dom.isBR(node)) {
|
|
node = r.insertNode(br, true);
|
|
if (isFormatNode(last.firstChild) && $(last).closest(options.styleTags.join(',')).length) {
|
|
dom.moveContent(last.firstChild, last);
|
|
last.removeChild(last.firstChild);
|
|
}
|
|
do {
|
|
node = dom.hasContentAfter(node);
|
|
} while (node && dom.isBR(node));
|
|
|
|
// create an other br because the user can't see the new line with only br in a block
|
|
if (!node && (!br.nextElementSibling || !dom.isBR(br.nextElementSibling))) {
|
|
$(br).before($("<br/>")[0]);
|
|
}
|
|
node = br.nextSibling || br;
|
|
} else if (last === node && dom.isBR(node)) {
|
|
$(node).after(br);
|
|
node = br;
|
|
} else if (!r.so && r.isOnList() && !r.sc.textContent.length && !dom.ancestor(r.sc, dom.isLi).nextElementSibling) {
|
|
// double enter on the end of a list = new line out of the list
|
|
$('<p></p>').append(br).insertAfter(dom.ancestor(r.sc, dom.isList));
|
|
node = br;
|
|
} else if (dom.isBR(exist) && $(r.sc).closest('blockquote, pre').length && !dom.hasContentAfter($(exist.parentNode).closest('blockquote *, pre *').length ? exist.parentNode : exist)) {
|
|
// double enter on the end of a blockquote & pre = new line out of the list
|
|
$('<p></p>').append(br).insertAfter($(r.sc).closest('blockquote, pre'));
|
|
node = br;
|
|
} else if (dom.isEditable(dom.node(r.sc))) {
|
|
// if we are directly in an editable, only SHIFT + ENTER should add a newline
|
|
node = null;
|
|
} else if (last === r.sc) {
|
|
if (dom.isBR(last)) {
|
|
last = last.parentNode;
|
|
}
|
|
$node = $(last);
|
|
$clone = $node.clone().text("");
|
|
$node.after($clone);
|
|
node = dom.node(dom.firstElementChild($clone[0]) || $clone[0]);
|
|
$(node).html(br);
|
|
node = br;
|
|
} else {
|
|
node = dom.splitTree(last, {'node': r.sc, 'offset': r.so}) || r.sc;
|
|
if (!contentBefore) {
|
|
var cur = dom.node(dom.lastChild(node.previousSibling));
|
|
if (!dom.isBR(cur)) {
|
|
$(cur).html(br);
|
|
}
|
|
}
|
|
if (!dom.isVisibleText(node)) {
|
|
node = dom.firstChild(node);
|
|
$(dom.node( dom.isBR(node) ? node.parentNode : node )).html(br);
|
|
node = br;
|
|
}
|
|
}
|
|
|
|
if (node) {
|
|
node = dom.firstChild(node);
|
|
if (dom.isBR(node)) {
|
|
range.createFromNode(node).select();
|
|
} else {
|
|
range.create(node,0).select();
|
|
}
|
|
dom.scrollIntoViewIfNeeded(node);
|
|
}
|
|
event.preventDefault();
|
|
return false;
|
|
};
|
|
$.summernote.pluginEvents.visible = function (event, editor, layoutInfo) {
|
|
var $editable = layoutInfo.editable();
|
|
$editable.data('NoteHistory').recordUndo($editable, "visible");
|
|
|
|
var r = range.create();
|
|
if (!r) return;
|
|
|
|
if (!r.isCollapsed()) {
|
|
if ((dom.isCell(dom.node(r.sc)) || dom.isCell(dom.node(r.ec))) && dom.node(r.sc) !== dom.node(r.ec)) {
|
|
remove_table_content(r);
|
|
r = range.create(r.ec, 0);
|
|
}
|
|
r.select();
|
|
}
|
|
|
|
// don't write in forbidden tag (like span for font awsome)
|
|
var node = dom.firstChild(r.sc.tagName && r.so ? r.sc.childNodes[r.so] || r.sc : r.sc);
|
|
while (node.parentNode) {
|
|
if (dom.isForbiddenNode(node)) {
|
|
var text = node.previousSibling;
|
|
if (text && dom.isText(text) && dom.isVisibleText(text)) {
|
|
range.create(text, text.textContent.length, text, text.textContent.length).select();
|
|
} else {
|
|
text = node.parentNode.insertBefore(document.createTextNode( "." ), node);
|
|
range.create(text, 1, text, 1).select();
|
|
setTimeout(function () {
|
|
var text = range.create().sc;
|
|
text.textContent = text.textContent.replace(/^./, '');
|
|
range.create(text, text.textContent.length, text, text.textContent.length).select();
|
|
},0);
|
|
}
|
|
break;
|
|
}
|
|
node = node.parentNode;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
function remove_table_content(r) {
|
|
var nodes = dom.listBetween(r.sc, r.ec, r.so, r.eo);
|
|
if (dom.isText(r.sc)) {
|
|
r.sc.textContent = r.sc.textContent.slice(0, r.so);
|
|
}
|
|
if (dom.isText(r.ec)) {
|
|
r.ec.textContent = r.ec.textContent.slice(r.eo);
|
|
}
|
|
for (var i in nodes) {
|
|
var node = nodes[i];
|
|
if (node === r.sc || node === r.ec || $.contains(node, r.sc) || $.contains(node, r.ec)) {
|
|
continue;
|
|
} else if (dom.isCell(node)) {
|
|
$(node).html("<br/>");
|
|
} else if (node.parentNode) {
|
|
do {
|
|
var parent = node.parentNode;
|
|
parent.removeChild(node);
|
|
node = parent;
|
|
} while (!dom.isVisibleText(node) && !dom.firstElementChild(node) &&
|
|
!dom.isCell(node) &&
|
|
node.parentNode && !$(node.parentNode).hasClass('o_editable'));
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
$.summernote.pluginEvents.delete = function (event, editor, layoutInfo) {
|
|
var $editable = layoutInfo.editable();
|
|
$editable.data('NoteHistory').recordUndo($editable, "delete");
|
|
|
|
var r = range.create();
|
|
if (!r) return;
|
|
if (!r.isContentEditable()) {
|
|
event.preventDefault();
|
|
return false;
|
|
}
|
|
if (!r.isCollapsed()) {
|
|
if (dom.isCell(dom.node(r.sc)) || dom.isCell(dom.node(r.ec))) {
|
|
remove_table_content(r);
|
|
range.create(r.ec, 0).select();
|
|
} else {
|
|
r = r.deleteContents();
|
|
r.select();
|
|
}
|
|
event.preventDefault();
|
|
return false;
|
|
}
|
|
|
|
var target = r.ec;
|
|
var offset = r.eo;
|
|
if (target.tagName && target.childNodes[offset]) {
|
|
target = target.childNodes[offset];
|
|
offset = 0;
|
|
}
|
|
|
|
var node = dom.node(target);
|
|
var data = dom.merge(node, target, offset, target, offset, null, true);
|
|
data = dom.removeSpace(node.parentNode, data.sc, data.so, data.ec, data.eo);
|
|
r = range.create(data.sc, data.so);
|
|
r.select();
|
|
target = r.sc;
|
|
offset = r.so;
|
|
|
|
while (!dom.hasContentAfter(node) && !dom.hasContentBefore(node) && !dom.isImg(node)) {node = node.parentNode;}
|
|
|
|
var contentAfter = target.textContent.slice(offset,Infinity).match(/\S|\u00A0/);
|
|
var content = target.textContent.replace(/[ \t\r\n]+$/, '');
|
|
var temp;
|
|
var temp2;
|
|
var next;
|
|
|
|
// media
|
|
if (dom.isImg(node) || (!contentAfter && dom.isImg(dom.hasContentAfter(node)))) {
|
|
var parent;
|
|
var index;
|
|
if (!dom.isImg(node)) {
|
|
node = dom.hasContentAfter(node);
|
|
}
|
|
while (dom.isImg(node)) {
|
|
parent = node.parentNode;
|
|
index = dom.position(node);
|
|
if (index>0) {
|
|
next = node.previousSibling;
|
|
r = range.create(next, next.textContent.length);
|
|
} else {
|
|
r = range.create(parent, 0);
|
|
}
|
|
if (!dom.hasContentAfter(node) && !dom.hasContentBefore(node)) {
|
|
parent.appendChild($('<br/>')[0]);
|
|
}
|
|
parent.removeChild(node);
|
|
node = parent;
|
|
r.select();
|
|
}
|
|
}
|
|
// empty tag
|
|
else if (!content.length && target.tagName && dom.isRemovableEmptyNode(dom.isBR(target) ? target.parentNode : target)) {
|
|
if (node === $editable[0] || $.contains(node, $editable[0])) {
|
|
event.preventDefault();
|
|
return false;
|
|
}
|
|
var before = false;
|
|
next = dom.hasContentAfter(dom.ancestorHaveNextSibling(node));
|
|
if (!dom.isContentEditable(next)) {
|
|
before = true;
|
|
next = dom.hasContentBefore(dom.ancestorHavePreviousSibling(node));
|
|
}
|
|
dom.removeSpace(next.parentNode, next, 0, next, 0); // clean before jump for not select invisible space between 2 tag
|
|
next = dom.firstChild(next);
|
|
node.parentNode.removeChild(node);
|
|
range.create(next, before ? next.textContent.length : 0).select();
|
|
}
|
|
// normal feature if same tag and not the end
|
|
else if (contentAfter) {
|
|
return true;
|
|
}
|
|
// merge with the next text node
|
|
else if (dom.isText(target) && (temp = dom.hasContentAfter(target)) && dom.isText(temp)) {
|
|
return true;
|
|
}
|
|
//merge with the next block
|
|
else if ((temp = dom.ancestorHaveNextSibling(target)) &&
|
|
!r.isOnCell() &&
|
|
dom.isMergable(temp) &&
|
|
dom.isMergable(temp2 = dom.hasContentAfter(temp)) &&
|
|
temp.tagName === temp2.tagName &&
|
|
(temp.tagName !== "LI" || !$('ul,ol', temp).length) && (temp2.tagName !== "LI" || !$('ul,ol', temp2).length) && // protect li
|
|
!dom.isNotBreakable(temp) &&
|
|
!dom.isNotBreakable(temp2)) {
|
|
dom.autoMerge(target, false);
|
|
next = dom.firstChild(dom.hasContentAfter(dom.ancestorHaveNextSibling(target)));
|
|
if (dom.isBR(next)) {
|
|
if (dom.position(next) === 0) {
|
|
range.create(next.parentNode, 0).select();
|
|
}
|
|
else {
|
|
range.create(next.previousSibling, next.previousSibling.textContent.length).select();
|
|
}
|
|
next.parentNode.removeChild(next);
|
|
} else {
|
|
range.create(next, 0).select();
|
|
}
|
|
}
|
|
// jump to next node for delete
|
|
else if ((temp = dom.ancestorHaveNextSibling(target)) && (temp2 = dom.hasContentAfter(temp)) && dom.isContentEditable(temp2)) {
|
|
|
|
dom.removeSpace(temp2.parentNode, temp2, 0, temp, 0); // clean before jump for not select invisible space between 2 tag
|
|
temp2 = dom.firstChild(temp2);
|
|
|
|
r = range.create(temp2, 0);
|
|
r.select();
|
|
|
|
if ((dom.isText(temp) || dom.getComputedStyle(temp).display === "inline") && (dom.isText(temp2) || dom.getComputedStyle(temp2).display === "inline")) {
|
|
if (dom.isText(temp2)) {
|
|
temp2.textContent = temp2.textContent.replace(/^\s*\S/, '');
|
|
} else {
|
|
$.summernote.pluginEvents.delete(event, editor, layoutInfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
$(dom.node(r.sc)).trigger("click"); // trigger click to disable and reanable editor and image handler
|
|
event.preventDefault();
|
|
return false;
|
|
};
|
|
$.summernote.pluginEvents.backspace = function (event, editor, layoutInfo) {
|
|
var $editable = layoutInfo.editable();
|
|
$editable.data('NoteHistory').recordUndo($editable, "backspace");
|
|
|
|
var r = range.create();
|
|
if (!r) return;
|
|
if (!r.isContentEditable()) {
|
|
event.preventDefault();
|
|
return false;
|
|
}
|
|
if (!r.isCollapsed()) {
|
|
if (dom.isCell(dom.node(r.sc)) || dom.isCell(dom.node(r.ec))) {
|
|
remove_table_content(r);
|
|
range.create(r.sc, dom.nodeLength(r.sc)).select();
|
|
} else {
|
|
r = r.deleteContents();
|
|
r.select();
|
|
}
|
|
event.preventDefault();
|
|
return false;
|
|
}
|
|
|
|
var target = r.sc;
|
|
var offset = r.so;
|
|
if (target.tagName && target.childNodes[offset]) {
|
|
target = target.childNodes[offset];
|
|
offset = 0;
|
|
}
|
|
|
|
var node = dom.node(target);
|
|
var data = dom.merge(node, target, offset, target, offset, null, true);
|
|
data = dom.removeSpace(node.parentNode, data.sc, data.so, data.ec, data.eo);
|
|
r = dom.isVoid(data.sc) ? range.createFromNode(data.sc) : range.create(data.sc, data.so);
|
|
r.select();
|
|
target = r.sc;
|
|
offset = r.so;
|
|
if (target.tagName && target.childNodes[offset]) {
|
|
target = target.childNodes[offset];
|
|
offset = 0;
|
|
node = dom.node(target);
|
|
}
|
|
|
|
while (node.parentNode && !dom.hasContentAfter(node) && !dom.hasContentBefore(node) && !dom.isImg(node)) {node = node.parentNode;}
|
|
|
|
var contentBefore = target.textContent.slice(0,offset).match(/\S|\u00A0/);
|
|
var content = target.textContent.replace(/[ \t\r\n]+$/, '');
|
|
var temp;
|
|
var temp2;
|
|
var prev;
|
|
|
|
// delete media
|
|
if (dom.isImg(node) || (!contentBefore && dom.isImg(dom.hasContentBefore(node)))) {
|
|
if (!dom.isImg(node)) {
|
|
node = dom.hasContentBefore(node);
|
|
}
|
|
range.createFromNode(node).select();
|
|
$.summernote.pluginEvents.delete(event, editor, layoutInfo);
|
|
}
|
|
// table tr td
|
|
else if (r.isOnCell() && !offset && (target === (temp = dom.ancestor(target, dom.isCell)) || target === temp.firstChild || (dom.isText(temp.firstChild) && !dom.isVisibleText(temp.firstChild) && target === temp.firstChild.nextSibling))) {
|
|
if (dom.previousElementSibling(temp)) {
|
|
var td = dom.previousElementSibling(temp);
|
|
node = td.lastChild || td;
|
|
} else {
|
|
var tr = temp.parentNode;
|
|
var prevTr = dom.previousElementSibling(tr);
|
|
if (!$(temp.parentNode).text().match(/\S|\u00A0/)) {
|
|
if (prevTr) {
|
|
node = dom.lastChild(dom.lastElementChild(prevTr));
|
|
} else {
|
|
node = dom.lastChild(dom.hasContentBefore(dom.ancestorHavePreviousSibling(tr)) || $editable.get(0));
|
|
}
|
|
$(tr).empty();
|
|
if (!$(tr).closest('table').has('td, th').length) {
|
|
$(tr).closest('table').remove();
|
|
}
|
|
$(tr).remove();
|
|
range.create(node, node.textContent.length, node, node.textContent.length).select();
|
|
} else {
|
|
node = dom.lastElementChild(prevTr).lastChild || dom.lastElementChild(prevTr);
|
|
}
|
|
}
|
|
if (dom.isBR(node)) {
|
|
range.createFromNode(node).select();
|
|
} else {
|
|
range.create(node, dom.nodeLength(node)).select();
|
|
}
|
|
}
|
|
// empty tag
|
|
else if (!content.length && target.tagName && dom.isRemovableEmptyNode(target)) {
|
|
if (node === $editable[0] || $.contains(node, $editable[0])) {
|
|
event.preventDefault();
|
|
return false;
|
|
}
|
|
var before = true;
|
|
prev = dom.hasContentBefore(dom.ancestorHavePreviousSibling(node));
|
|
if (!dom.isContentEditable(prev)) {
|
|
before = false;
|
|
prev = dom.hasContentAfter(dom.ancestorHaveNextSibling(node));
|
|
}
|
|
dom.removeSpace(prev.parentNode, prev, 0, prev, 0); // clean before jump for not select invisible space between 2 tag
|
|
prev = dom.lastChild(prev);
|
|
node.parentNode.removeChild(node);
|
|
range.createFromNode(prev).select();
|
|
range.create(prev, before ? prev.textContent.length : 0).select();
|
|
}
|
|
// normal feature if same tag and not the begin
|
|
else if (contentBefore) {
|
|
return true;
|
|
}
|
|
// merge with the previous text node
|
|
else if (dom.isText(target) && (temp = dom.hasContentBefore(target)) && (dom.isText(temp) || dom.isBR(temp))) {
|
|
return true;
|
|
}
|
|
//merge with the previous block
|
|
else if ((temp = dom.ancestorHavePreviousSibling(target)) &&
|
|
dom.isMergable(temp) &&
|
|
dom.isMergable(temp2 = dom.hasContentBefore(temp)) &&
|
|
temp.tagName === temp2.tagName &&
|
|
(temp.tagName !== "LI" || !$('ul,ol', temp).length) && (temp2.tagName !== "LI" || !$('ul,ol', temp2).length) && // protect li
|
|
!dom.isNotBreakable(temp) &&
|
|
!dom.isNotBreakable(temp2)) {
|
|
prev = dom.firstChild(target);
|
|
dom.autoMerge(target, true);
|
|
range.create(prev, 0).select();
|
|
}
|
|
// jump to previous node for delete
|
|
else if ((temp = dom.ancestorHavePreviousSibling(target)) && (temp2 = dom.hasContentBefore(temp)) && dom.isContentEditable(temp2)) {
|
|
|
|
dom.removeSpace(temp2.parentNode, temp2, 0, temp, 0); // clean before jump for not select invisible space between 2 tag
|
|
temp2 = dom.lastChild(temp2);
|
|
|
|
r = range.create(temp2, temp2.textContent.length, temp2, temp2.textContent.length);
|
|
r.select();
|
|
|
|
if ((dom.isText(temp) || dom.getComputedStyle(temp).display === "inline") && (dom.isText(temp2) || dom.getComputedStyle(temp2).display === "inline")) {
|
|
if (dom.isText(temp2)) {
|
|
temp2.textContent = temp2.textContent.replace(/\S\s*$/, '');
|
|
} else {
|
|
$.summernote.pluginEvents.backspace(event, editor, layoutInfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
r = range.create();
|
|
if (r) {
|
|
$(dom.node(r.sc)).trigger("click"); // trigger click to disable and reanable editor and image handler
|
|
dom.scrollIntoViewIfNeeded(r.sc.parentNode.previousElementSibling || r.sc);
|
|
}
|
|
|
|
event.preventDefault();
|
|
return false;
|
|
};
|
|
|
|
function isFormatNode(node) {
|
|
return node.tagName && options.styleTags.indexOf(node.tagName.toLowerCase()) !== -1;
|
|
}
|
|
|
|
$.summernote.pluginEvents.insertUnorderedList = function (event, editor, layoutInfo, sorted) {
|
|
var $editable = layoutInfo.editable();
|
|
$editable.data('NoteHistory').recordUndo($editable);
|
|
|
|
var parent;
|
|
var r = range.create();
|
|
if (!r) return;
|
|
var node = r.sc;
|
|
while (node && node !== $editable[0]) {
|
|
|
|
parent = node.parentNode;
|
|
if (node.tagName === (sorted ? "UL" : "OL")) {
|
|
|
|
var ul = document.createElement(sorted ? "ol" : "ul");
|
|
ul.className = node.className;
|
|
parent.insertBefore(ul, node);
|
|
while (node.firstChild) {
|
|
ul.appendChild(node.firstChild);
|
|
}
|
|
parent.removeChild(node);
|
|
r.select();
|
|
return;
|
|
|
|
} else if (node.tagName === (sorted ? "OL" : "UL")) {
|
|
|
|
var lis = [];
|
|
for (var i=0; i<node.children.length; i++) {
|
|
lis.push(node.children[i]);
|
|
}
|
|
|
|
if (parent.tagName === "LI") {
|
|
node = parent;
|
|
parent = node.parentNode;
|
|
_.each(lis, function (li) {
|
|
parent.insertBefore(li, node);
|
|
});
|
|
} else {
|
|
_.each(lis, function (li) {
|
|
while (li.firstChild) {
|
|
parent.insertBefore(li.firstChild, node);
|
|
}
|
|
});
|
|
}
|
|
|
|
parent.removeChild(node);
|
|
r.select();
|
|
return;
|
|
|
|
}
|
|
node = parent;
|
|
}
|
|
|
|
var p0 = r.sc;
|
|
while (p0 && p0.parentNode && p0.parentNode !== $editable[0] && !isFormatNode(p0)) {
|
|
p0 = p0.parentNode;
|
|
}
|
|
if (!p0) return;
|
|
var p1 = r.ec;
|
|
while (p1 && p1.parentNode && p1.parentNode !== $editable[0] && !isFormatNode(p1)) {
|
|
p1 = p1.parentNode;
|
|
}
|
|
if (!p0.parentNode || p0.parentNode !== p1.parentNode) {
|
|
return;
|
|
}
|
|
|
|
parent = p0.parentNode;
|
|
ul = document.createElement(sorted ? "ol" : "ul");
|
|
parent.insertBefore(ul, p0);
|
|
var childNodes = parent.childNodes;
|
|
var brs = [];
|
|
var begin = false;
|
|
for (i = 0; i < childNodes.length; i++) {
|
|
if (begin && dom.isBR(childNodes[i])) {
|
|
parent.removeChild(childNodes[i]);
|
|
i--;
|
|
}
|
|
if ((!dom.isText(childNodes[i]) && !isFormatNode(childNodes[i])) || (!ul.firstChild && childNodes[i] !== p0) ||
|
|
$.contains(ul, childNodes[i]) || (dom.isText(childNodes[i]) && !childNodes[i].textContent.match(/\S|u00A0/))) {
|
|
continue;
|
|
}
|
|
begin = true;
|
|
var li = document.createElement('li');
|
|
ul.appendChild(li);
|
|
li.appendChild(childNodes[i]);
|
|
if (li.firstChild === p1) {
|
|
break;
|
|
}
|
|
i--;
|
|
}
|
|
if (dom.isBR(childNodes[i])) {
|
|
parent.removeChild(childNodes[i]);
|
|
}
|
|
|
|
for (i = 0; i < brs.length ; i++) {
|
|
parent.removeChild(brs[i]);
|
|
}
|
|
r.clean().select();
|
|
event.preventDefault();
|
|
return false;
|
|
};
|
|
$.summernote.pluginEvents.insertOrderedList = function (event, editor, layoutInfo) {
|
|
return $.summernote.pluginEvents.insertUnorderedList(event, editor, layoutInfo, true);
|
|
};
|
|
$.summernote.pluginEvents.indent = function (event, editor, layoutInfo, outdent) {
|
|
var $editable = layoutInfo.editable();
|
|
$editable.data('NoteHistory').recordUndo($editable);
|
|
var r = range.create();
|
|
if (!r) return;
|
|
|
|
var flag = false;
|
|
function indentUL(UL, start, end) {
|
|
var next;
|
|
var tagName = UL.tagName;
|
|
var node = UL.firstChild;
|
|
var ul = document.createElement(tagName);
|
|
var li = document.createElement("li");
|
|
li.style.listStyle = "none";
|
|
li.appendChild(ul);
|
|
|
|
if (flag) {
|
|
flag = 1;
|
|
}
|
|
|
|
// create and fill ul into a li
|
|
while (node) {
|
|
if (flag === 1 || node === start || $.contains(node, start)) {
|
|
flag = true;
|
|
node.parentNode.insertBefore(li, node);
|
|
}
|
|
next = dom.nextElementSibling(node);
|
|
if (flag) {
|
|
ul.appendChild(node);
|
|
}
|
|
if (node === end || $.contains(node, end)) {
|
|
flag = false;
|
|
break;
|
|
}
|
|
node = next;
|
|
}
|
|
|
|
var temp;
|
|
var prev = dom.previousElementSibling(li);
|
|
if (prev && prev.tagName === "LI" && (temp = dom.firstElementChild(prev)) && temp.tagName === tagName && ((dom.firstElementChild(prev) || prev.firstChild) !== ul)) {
|
|
dom.doMerge(dom.firstElementChild(prev) || prev.firstChild, ul);
|
|
li = prev;
|
|
li.parentNode.removeChild(dom.nextElementSibling(li));
|
|
}
|
|
next = dom.nextElementSibling(li);
|
|
if (next && next.tagName === "LI" && (temp = dom.firstElementChild(next)) && temp.tagName === tagName && (dom.firstElementChild(li) !== dom.firstElementChild(next))) {
|
|
dom.doMerge(dom.firstElementChild(li), dom.firstElementChild(next));
|
|
li.parentNode.removeChild(dom.nextElementSibling(li));
|
|
}
|
|
}
|
|
function outdenttUL(UL, start, end) {
|
|
var next;
|
|
var node = UL.firstChild;
|
|
var parent = UL.parentNode;
|
|
var li = UL.parentNode.tagName === "LI" ? UL.parentNode : UL;
|
|
var ul = UL.parentNode.tagName === "LI" ? UL.parentNode.parentNode : UL.parentNode;
|
|
start = dom.ancestor(start, dom.isLi);
|
|
end = dom.ancestor(end, dom.isLi);
|
|
|
|
if (ul.tagName !== "UL" && ul.tagName !== "OL") return;
|
|
|
|
// create and fill ul into a li
|
|
while (node) {
|
|
if (node === start || $.contains(node, start)) {
|
|
flag = true;
|
|
if (dom.previousElementSibling(node) && li.tagName === "LI") {
|
|
li = dom.splitTree(li, dom.prevPoint({'node': node, 'offset': 0}));
|
|
}
|
|
}
|
|
next = dom.nextElementSibling(node);
|
|
if (flag) {
|
|
ul = node.parentNode;
|
|
li.parentNode.insertBefore(node, li);
|
|
if (!ul.children.length) {
|
|
if (ul.parentNode.tagName === "LI") {
|
|
ul = ul.parentNode;
|
|
}
|
|
ul.parentNode.removeChild(ul);
|
|
}
|
|
}
|
|
|
|
if (node === end || $.contains(node, end)) {
|
|
flag = false;
|
|
break;
|
|
}
|
|
node = next;
|
|
}
|
|
|
|
dom.merge(parent, start, 0, end, 1, null, true);
|
|
}
|
|
function indentOther(p, start, end) {
|
|
if (p === start || $.contains(p, start) || $.contains(start, p)) {
|
|
flag = true;
|
|
}
|
|
if (flag) {
|
|
if (outdent) {
|
|
dom.outdent(p);
|
|
} else {
|
|
dom.indent(p);
|
|
}
|
|
}
|
|
if (p === end || $.contains(p, end) || $.contains(end, p)) {
|
|
flag = false;
|
|
}
|
|
}
|
|
|
|
var ancestor = r.commonAncestor();
|
|
var $dom = $(ancestor);
|
|
|
|
if (!dom.isList(ancestor)) {
|
|
// to indent a selection, we indent the child nodes of the common
|
|
// ancestor that contains this selection
|
|
$dom = $(dom.node(ancestor)).children();
|
|
}
|
|
if (!$dom.not('br').length) {
|
|
// if selection is inside a list, we indent its list items
|
|
$dom = $(dom.ancestor(r.sc, dom.isList));
|
|
if (!$dom.length) {
|
|
// if the selection is contained in a single HTML node, we indent
|
|
// the first ancestor 'content block' (P, H1, PRE, ...) or TD
|
|
$dom = $(r.sc).closest(options.styleTags.join(',')+',td');
|
|
}
|
|
}
|
|
|
|
// if select tr, take the first td
|
|
$dom = $dom.map(function () { return this.tagName === "TR" ? dom.firstElementChild(this) : this; });
|
|
|
|
$dom.each(function () {
|
|
if (flag || $.contains(this, r.sc)) {
|
|
if (dom.isList(this)) {
|
|
if (outdent) {
|
|
outdenttUL(this, r.sc, r.ec);
|
|
} else {
|
|
indentUL(this, r.sc, r.ec);
|
|
}
|
|
} else if (isFormatNode(this) || dom.ancestor(this, dom.isCell)) {
|
|
indentOther(this, r.sc, r.ec);
|
|
}
|
|
}
|
|
});
|
|
|
|
if ($dom.length) {
|
|
var $parent = $dom.parent();
|
|
|
|
// remove text nodes between lists
|
|
var $ul = $parent.find('ul, ol');
|
|
if (!$ul.length) {
|
|
$ul = $(dom.ancestor(r.sc, dom.isList));
|
|
}
|
|
$ul.each(function () {
|
|
if (this.previousSibling &&
|
|
this.previousSibling !== dom.previousElementSibling(this) &&
|
|
!this.previousSibling.textContent.match(/\S/)) {
|
|
this.parentNode.removeChild(this.previousSibling);
|
|
}
|
|
if (this.nextSibling &&
|
|
this.nextSibling !== dom.nextElementSibling(this) &&
|
|
!this.nextSibling.textContent.match(/\S/)) {
|
|
this.parentNode.removeChild(this.nextSibling);
|
|
}
|
|
});
|
|
|
|
// merge same ul or ol
|
|
r = dom.merge($parent[0], r.sc, r.so, r.ec, r.eo, function (prev, cur) {
|
|
if (prev && dom.isList(prev) && dom.isEqual(prev, cur)) {
|
|
return true;
|
|
}
|
|
}, true);
|
|
range.create(r.sc, r.so, r.ec, r.eo).select();
|
|
}
|
|
event.preventDefault();
|
|
return false;
|
|
};
|
|
$.summernote.pluginEvents.outdent = function (event, editor, layoutInfo) {
|
|
return $.summernote.pluginEvents.indent(event, editor, layoutInfo, true);
|
|
};
|
|
|
|
$.summernote.pluginEvents.formatBlock = function (event, editor, layoutInfo, sTagName) {
|
|
$.summernote.pluginEvents.applyFont(event, editor, layoutInfo, null, null, "Default");
|
|
var $editable = layoutInfo.editable();
|
|
$editable.data('NoteHistory').recordUndo($editable);
|
|
event.preventDefault();
|
|
|
|
var r = range.create();
|
|
if (!r) {
|
|
return;
|
|
}
|
|
r.reRange().select();
|
|
|
|
if (sTagName === "blockquote" || sTagName === "pre") {
|
|
sTagName = $.summernote.core.agent.isMSIE ? '<' + sTagName + '>' : sTagName;
|
|
document.execCommand('FormatBlock', false, sTagName);
|
|
return;
|
|
}
|
|
|
|
// fix by flectra because if you select a style in a li with no p tag all the ul is wrapped by the style tag
|
|
var nodes = dom.listBetween(r.sc, r.ec, r.so, r.eo);
|
|
for (var i=0; i<nodes.length; i++) {
|
|
if (dom.isBR(nodes[i]) || (dom.isText(nodes[i]) && dom.isVisibleText(nodes[i])) || dom.isB(nodes[i]) || dom.isU(nodes[i]) || dom.isS(nodes[i]) || dom.isI(nodes[i]) || dom.isFont(nodes[i])) {
|
|
var ancestor = dom.ancestor(nodes[i], isFormatNode);
|
|
if (!ancestor) {
|
|
dom.wrap(nodes[i], sTagName);
|
|
} else if (ancestor.tagName.toLowerCase() !== sTagName) {
|
|
var tag = document.createElement(sTagName);
|
|
ancestor.parentNode.insertBefore(tag, ancestor);
|
|
dom.moveContent(ancestor, tag);
|
|
if (ancestor.className) {
|
|
tag.className = ancestor.className;
|
|
}
|
|
ancestor.parentNode.removeChild(ancestor);
|
|
}
|
|
}
|
|
}
|
|
r.select();
|
|
};
|
|
$.summernote.pluginEvents.removeFormat = function (event, editor, layoutInfo, value) {
|
|
var $editable = layoutInfo.editable();
|
|
$editable.data('NoteHistory').recordUndo($editable);
|
|
var r = range.create();
|
|
if (!r) return;
|
|
var node = range.create().sc.parentNode;
|
|
document.execCommand('removeFormat');
|
|
document.execCommand('removeFormat');
|
|
r = range.create();
|
|
if (!r) return;
|
|
r = dom.merge(node, r.sc, r.so, r.ec, r.eo, null, true);
|
|
range.create(r.sc, r.so, r.ec, r.eo).select();
|
|
event.preventDefault();
|
|
return false;
|
|
};
|
|
var fn_boutton_updateRecentColor = eventHandler.modules.toolbar.button.updateRecentColor;
|
|
eventHandler.modules.toolbar.button.updateRecentColor = function (elBtn, sEvent, sValue) {
|
|
fn_boutton_updateRecentColor.call(this, elBtn, sEvent, sValue);
|
|
var $recentcolor = $.find('.note-recent-color i');
|
|
var $recentcolorbtn = $.find('.note-recent-color');
|
|
|
|
//find last used color for fonts or for icons
|
|
//set this color into recentcolor button of both font and icon
|
|
for (var i in $recentcolor) {
|
|
var $font = $recentcolor[i];
|
|
var $button = $($recentcolorbtn[i]);
|
|
var className = $font.className.split(/\s+/);
|
|
var k;
|
|
if (sEvent === "foreColor") {
|
|
//set class for forecolor to recentcolor button font-icon
|
|
for (k=2; k<className.length; k++) {
|
|
if (className[k].length && className[k].slice(0,5) === "text-") {
|
|
className.splice(k,1);
|
|
k--;
|
|
}
|
|
}
|
|
if (sValue.indexOf('text-') !== -1) {
|
|
$font.className = className.join(' ') + ' ' + sValue;
|
|
$font.style.color = '';
|
|
} else {
|
|
$font.className = $font.className.replace(/(^|\s+)text-\S+/, '');
|
|
$font.style.color = sValue !== 'inherit' ? sValue : "";
|
|
}
|
|
} else {
|
|
//set class for backcolor to recentcolor button font-icon
|
|
for (k=2; k<className.length; k++) {
|
|
if (className[k].length && className[k].slice(0,3) === "bg-") {
|
|
className.splice(k,1);
|
|
k--;
|
|
}
|
|
}
|
|
if (sValue.indexOf('bg-') !== -1) {
|
|
$font.className =className.join(' ') + ' ' + sValue;
|
|
$font.style.backgroundColor = "";
|
|
} else {
|
|
$font.className = $font.className.replace(/(^|\s+)bg-\S+/, '');
|
|
$font.style.backgroundColor = sValue !== 'inherit' ? sValue : "";
|
|
}
|
|
}
|
|
if (sValue !== 'inherit') {
|
|
//set attribute for color to the recentcolor button
|
|
var colorInfo = JSON.parse($button.attr('data-value'));
|
|
colorInfo[sEvent] = sValue;
|
|
$button.attr('data-value', JSON.stringify(colorInfo));
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
$(document).on('click keyup', function () {
|
|
var current_range = {};
|
|
try {
|
|
current_range = range.create() || {};
|
|
} catch (e) {
|
|
// if range is on Restricted element ignore error
|
|
}
|
|
var $popover = $(current_range.sc).closest('[contenteditable]');
|
|
var popover_history = ($popover.data()||{}).NoteHistory;
|
|
if (!popover_history || popover_history === history) return;
|
|
var editor = $popover.parent('.note-editor');
|
|
$('button[data-event="undo"]', editor).attr('disabled', !popover_history.hasUndo());
|
|
$('button[data-event="redo"]', editor).attr('disabled', !popover_history.hasRedo());
|
|
});
|
|
|
|
eventHandler.modules.editor.undo = function ($popover) {
|
|
if (!$popover.attr('disabled')) $popover.data('NoteHistory').undo();
|
|
};
|
|
eventHandler.modules.editor.redo = function ($popover) {
|
|
if (!$popover.attr('disabled')) $popover.data('NoteHistory').redo();
|
|
};
|
|
|
|
// use image toolbar if current range is on image
|
|
var fn_editor_currentstyle = eventHandler.modules.editor.currentStyle;
|
|
eventHandler.modules.editor.currentStyle = function (target) {
|
|
var styleInfo = fn_editor_currentstyle.apply(this, arguments);
|
|
// with our changes for inline editor, the targeted element could be a button of the editor
|
|
if (!styleInfo.image || !dom.isEditable(styleInfo.image)) {
|
|
styleInfo.image = undefined;
|
|
var r = range.create();
|
|
if (r)
|
|
styleInfo.image = r.isOnImg();
|
|
}
|
|
// Fix when the target is a link: the text-align buttons state should
|
|
// indicate the alignment of the link in the parent, not the text inside
|
|
// the link (which is not possible to customize with summernote). Summernote fixed
|
|
// this in their newest version... by just not showing the active button
|
|
// for alignments.
|
|
if (styleInfo.anchor) {
|
|
styleInfo['text-align'] = $(styleInfo.anchor).parent().css('text-align');
|
|
}
|
|
return styleInfo;
|
|
};
|
|
|
|
options.fontSizes = [_t('Default'), 8, 9, 10, 11, 12, 14, 18, 24, 36, 48, 62];
|
|
$.summernote.pluginEvents.applyFont = function (event, editor, layoutInfo, color, bgcolor, size) {
|
|
var r = range.create();
|
|
if (!r) return;
|
|
var startPoint = r.getStartPoint();
|
|
var endPoint = r.getEndPoint();
|
|
|
|
if (r.isCollapsed() && !dom.isFont(r.sc)) {
|
|
return {
|
|
sc: startPoint.node,
|
|
so: startPoint.offset,
|
|
ec: endPoint.node,
|
|
offset: endPoint.offset
|
|
};
|
|
}
|
|
|
|
if (startPoint.node.tagName && startPoint.node.childNodes[startPoint.offset]) {
|
|
startPoint.node = startPoint.node.childNodes[startPoint.offset];
|
|
startPoint.offset = 0;
|
|
}
|
|
if (endPoint.node.tagName && endPoint.node.childNodes[endPoint.offset]) {
|
|
endPoint.node = endPoint.node.childNodes[endPoint.offset];
|
|
endPoint.offset = 0;
|
|
}
|
|
|
|
// get first and last point
|
|
var ancestor;
|
|
var node;
|
|
if (endPoint.offset && endPoint.offset !== dom.nodeLength(endPoint.node)) {
|
|
ancestor = dom.ancestor(endPoint.node, dom.isFont) || endPoint.node;
|
|
dom.splitTree(ancestor, endPoint);
|
|
}
|
|
if (startPoint.offset && startPoint.offset !== dom.nodeLength(startPoint.node)) {
|
|
ancestor = dom.ancestor(startPoint.node, dom.isFont) || startPoint.node;
|
|
node = dom.splitTree(ancestor, startPoint);
|
|
if (endPoint.node === startPoint.node) {
|
|
endPoint.node = node;
|
|
endPoint.offset = dom.nodeLength(node);
|
|
}
|
|
startPoint.node = node;
|
|
startPoint.offset = 0;
|
|
}
|
|
|
|
// get list of nodes to change
|
|
var nodes = [];
|
|
dom.walkPoint(startPoint, endPoint, function (point) {
|
|
var node = point.node;
|
|
if (((dom.isText(node) && dom.isVisibleText(node)) ||
|
|
(dom.isFont(node) && !dom.isVisibleText(node))) &&
|
|
(node !== endPoint.node || endPoint.offset)) {
|
|
|
|
nodes.push(point.node);
|
|
|
|
}
|
|
});
|
|
nodes = list.unique(nodes);
|
|
|
|
// If ico fa
|
|
if (r.isCollapsed()) {
|
|
nodes.push(startPoint.node);
|
|
}
|
|
|
|
// apply font: foreColor, backColor, size (the color can be use a class text-... or bg-...)
|
|
var font, $font, fonts = [], className;
|
|
var i;
|
|
if (color || bgcolor || size) {
|
|
for (i=0; i<nodes.length; i++) {
|
|
node = nodes[i];
|
|
|
|
font = dom.ancestor(node, dom.isFont);
|
|
if (!font) {
|
|
if (node.textContent.match(/^[ ]|[ ]$/)) {
|
|
node.textContent = node.textContent.replace(/^[ ]|[ ]$/g, '\u00A0');
|
|
}
|
|
|
|
font = dom.create("font");
|
|
node.parentNode.insertBefore(font, node);
|
|
font.appendChild(node);
|
|
}
|
|
|
|
fonts.push(font);
|
|
|
|
className = font.className.split(/\s+/);
|
|
|
|
var k;
|
|
if (color) {
|
|
for (k=0; k<className.length; k++) {
|
|
if (className[k].length && className[k].slice(0,5) === "text-") {
|
|
className.splice(k,1);
|
|
k--;
|
|
}
|
|
}
|
|
|
|
if (color.indexOf('text-') !== -1) {
|
|
font.className = className.join(" ") + " " + color;
|
|
font.style.color = "inherit";
|
|
} else {
|
|
font.className = className.join(" ");
|
|
font.style.color = color;
|
|
}
|
|
}
|
|
if (bgcolor) {
|
|
for (k=0; k<className.length; k++) {
|
|
if (className[k].length && className[k].slice(0,3) === "bg-") {
|
|
className.splice(k,1);
|
|
k--;
|
|
}
|
|
}
|
|
|
|
if (bgcolor.indexOf('bg-') !== -1) {
|
|
font.className = className.join(" ") + " " + bgcolor;
|
|
font.style.backgroundColor = "inherit";
|
|
} else {
|
|
font.className = className.join(" ");
|
|
font.style.backgroundColor = bgcolor;
|
|
}
|
|
}
|
|
if (size) {
|
|
font.style.fontSize = "inherit";
|
|
if (!isNaN(size) && Math.abs(parseInt(dom.getComputedStyle(font).fontSize, 10)-size)/size > 0.05) {
|
|
font.style.fontSize = size + "px";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove empty values
|
|
// we must remove the value in 2 steps (applay inherit then remove) because some
|
|
// browser like chrome have some time an error for the rendering and/or keep inherit
|
|
for (i=0; i<fonts.length; i++) {
|
|
font = fonts[i];
|
|
if (font.style.backgroundColor === "inherit") {
|
|
font.style.backgroundColor = "";
|
|
}
|
|
if (font.style.color === "inherit") {
|
|
font.style.color = "";
|
|
}
|
|
if (font.style.fontSize === "inherit") {
|
|
font.style.fontSize = "";
|
|
}
|
|
|
|
$font = $(font);
|
|
|
|
if (!$font.css("color") && !$font.css("background-color") && !$font.css("font-size")) {
|
|
$font.removeAttr("style");
|
|
}
|
|
if (!font.className.length) {
|
|
$font.removeAttr("class");
|
|
}
|
|
}
|
|
|
|
// select nodes to clean (to remove empty font and merge same nodes)
|
|
nodes = [];
|
|
dom.walkPoint(startPoint, endPoint, function (point) {
|
|
nodes.push(point.node);
|
|
});
|
|
nodes = list.unique(nodes);
|
|
|
|
function remove(node, to) {
|
|
if (node === endPoint.node) {
|
|
endPoint = dom.prevPoint(endPoint);
|
|
}
|
|
if (to) {
|
|
dom.moveContent(node, to);
|
|
}
|
|
dom.remove(node);
|
|
}
|
|
|
|
// remove node without attributes (move content), and merge the same nodes
|
|
var className2, style, style2;
|
|
for (i=0; i<nodes.length; i++) {
|
|
node = nodes[i];
|
|
|
|
if ((dom.isText(node) || dom.isBR(node)) && !dom.isVisibleText(node)) {
|
|
remove(node);
|
|
nodes.splice(i,1);
|
|
i--;
|
|
continue;
|
|
}
|
|
|
|
font = dom.ancestor(node, dom.isFont);
|
|
node = font || dom.ancestor(node, dom.isSpan);
|
|
|
|
if (!node) {
|
|
continue;
|
|
}
|
|
|
|
$font = $(node);
|
|
className = dom.orderClass(node);
|
|
style = dom.orderStyle(node);
|
|
|
|
if (!className && !style) {
|
|
remove(node, node.parentNode);
|
|
nodes.splice(i,1);
|
|
i--;
|
|
continue;
|
|
}
|
|
|
|
if (i>0 && (font = dom.ancestor(nodes[i-1], dom.isFont))) {
|
|
className2 = font.getAttribute('class');
|
|
style2 = font.getAttribute('style');
|
|
if (node !== font && className === className2 && style === style2) {
|
|
remove(node, font);
|
|
nodes.splice(i,1);
|
|
i--;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
range.create(startPoint.node, startPoint.offset, endPoint.node, endPoint.offset).select();
|
|
};
|
|
$.summernote.pluginEvents.fontSize = function (event, editor, layoutInfo, value) {
|
|
var $editable = layoutInfo.editable();
|
|
event.preventDefault();
|
|
$.summernote.pluginEvents.applyFont(event, editor, layoutInfo, null, null, value);
|
|
editor.afterCommand($editable);
|
|
};
|
|
$.summernote.pluginEvents.color = function (event, editor, layoutInfo, sObjColor) {
|
|
var oColor = JSON.parse(sObjColor);
|
|
var foreColor = oColor.foreColor, backColor = oColor.backColor;
|
|
|
|
if (foreColor) { $.summernote.pluginEvents.foreColor(event, editor, layoutInfo, foreColor); }
|
|
if (backColor) { $.summernote.pluginEvents.backColor(event, editor, layoutInfo, backColor); }
|
|
};
|
|
$.summernote.pluginEvents.foreColor = function (event, editor, layoutInfo, foreColor) {
|
|
var $editable = layoutInfo.editable();
|
|
$.summernote.pluginEvents.applyFont(event, editor, layoutInfo, foreColor, null, null);
|
|
editor.afterCommand($editable);
|
|
};
|
|
$.summernote.pluginEvents.backColor = function (event, editor, layoutInfo, backColor) {
|
|
var $editable = layoutInfo.editable();
|
|
var r = range.create();
|
|
if (!r) return;
|
|
if (r.isCollapsed() && r.isOnCell()) {
|
|
var cell = dom.ancestor(r.sc, dom.isCell);
|
|
cell.className = cell.className.replace(new RegExp('(^|\\s+)bg-[^\\s]+(\\s+|$)', 'gi'), '');
|
|
cell.style.backgroundColor = "";
|
|
if (backColor.indexOf('bg-') !== -1) {
|
|
cell.className += ' ' + backColor;
|
|
} else if (backColor !== 'inherit') {
|
|
cell.style.backgroundColor = backColor;
|
|
}
|
|
return;
|
|
}
|
|
$.summernote.pluginEvents.applyFont(event, editor, layoutInfo, null, backColor, null);
|
|
editor.afterCommand($editable);
|
|
};
|
|
|
|
options.onCreateLink = function (sLinkUrl) {
|
|
if (sLinkUrl.indexOf('mailto:') === 0 || sLinkUrl.indexOf('tel:') === 0) {
|
|
// pass
|
|
} else if (sLinkUrl.indexOf('@') !== -1 && sLinkUrl.indexOf(':') === -1) {
|
|
sLinkUrl = 'mailto:' + sLinkUrl;
|
|
} else if (sLinkUrl.indexOf('://') === -1 && sLinkUrl.indexOf('/') !== 0 && sLinkUrl.indexOf('#') !== 0) {
|
|
sLinkUrl = 'http://' + sLinkUrl;
|
|
}
|
|
return sLinkUrl;
|
|
};
|
|
|
|
function summernote_table_scroll(event) {
|
|
var r = range.create();
|
|
if (r && r.isOnCell()) {
|
|
$('.o_table_handler').remove();
|
|
}
|
|
}
|
|
function summernote_table_update(oStyle) {
|
|
var r = range.create();
|
|
if (!oStyle.range || !r || !r.isOnCell() || !r.isOnCellFirst()) {
|
|
$('.o_table_handler').remove();
|
|
return;
|
|
}
|
|
var table = dom.ancestor(oStyle.range.sc, dom.isTable);
|
|
if (!table) { // if the editable tag is inside the table
|
|
return;
|
|
}
|
|
var $editable = $(table).closest('.o_editable');
|
|
|
|
$('.o_table_handler').remove();
|
|
|
|
var $dels = $();
|
|
var $adds = $();
|
|
var $tds = $('tr:first', table).children();
|
|
$tds.each(function () {
|
|
var $td = $(this);
|
|
var pos = $td.offset();
|
|
|
|
var $del = $('<span class="o_table_handler fa fa-minus-square"/>').appendTo('body');
|
|
$del.data('td', this);
|
|
$dels = $dels.add($del);
|
|
$del.css({
|
|
left: ((pos.left + $td.outerWidth()/2)-6) + "px",
|
|
top: (pos.top-6) + "px"
|
|
});
|
|
|
|
var $add = $('<span class="o_table_handler fa fa-plus-square"/>').appendTo('body');
|
|
$add.data('td', this);
|
|
$adds = $adds.add($add);
|
|
$add.css({
|
|
left: (pos.left-6) + "px",
|
|
top: (pos.top-6) + "px"
|
|
});
|
|
});
|
|
|
|
var $last = $tds.last();
|
|
var pos = $last.offset();
|
|
var $add = $('<span class="o_table_handler fa fa-plus-square"/>').appendTo('body');
|
|
$adds = $adds.add($add);
|
|
$add.css({
|
|
left: (pos.left+$last.outerWidth()-6) + "px",
|
|
top: (pos.top-6) + "px"
|
|
});
|
|
|
|
var $table = $(table);
|
|
$dels.data('table', table).on('mousedown', function (event) {
|
|
var td = $(this).data('td');
|
|
$editable.data('NoteHistory').recordUndo($editable);
|
|
|
|
var newTd;
|
|
if ($(td).siblings().length) {
|
|
var eq = $(td).index();
|
|
$table.find('tr').each(function () {
|
|
$('> td:eq('+eq+')', this).remove();
|
|
});
|
|
newTd = $table.find('tr:first > td:eq('+eq+'), tr:first > td:last').first();
|
|
} else {
|
|
var prev = dom.lastChild(dom.hasContentBefore(dom.ancestorHavePreviousSibling($table[0])));
|
|
$table.remove();
|
|
$('.o_table_handler').remove();
|
|
r = range.create(prev, prev.textContent.length);
|
|
r.select();
|
|
$(r.sc).trigger('mouseup');
|
|
return;
|
|
}
|
|
|
|
$('.o_table_handler').remove();
|
|
range.create(newTd[0], 0, newTd[0], 0).select();
|
|
newTd.trigger('mouseup');
|
|
});
|
|
$adds.data('table', table).on('mousedown', function (event) {
|
|
var td = $(this).data('td');
|
|
$editable.data('NoteHistory').recordUndo($editable);
|
|
|
|
var newTd;
|
|
if (td) {
|
|
var eq = $(td).index();
|
|
$table.find('tr').each(function () {
|
|
$('td:eq('+eq+')', this).before('<td>'+dom.blank+'</td>');
|
|
});
|
|
newTd = $table.find('tr:first td:eq('+eq+')');
|
|
} else {
|
|
$table.find('tr').each(function () {
|
|
$(this).append('<td>'+dom.blank+'</td>');
|
|
});
|
|
newTd = $table.find('tr:first td:last');
|
|
}
|
|
|
|
$('.o_table_handler').remove();
|
|
range.create(newTd[0], 0, newTd[0], 0).select();
|
|
newTd.trigger('mouseup');
|
|
});
|
|
|
|
$dels.css({
|
|
'position': 'absolute',
|
|
'cursor': 'pointer',
|
|
'background-color': '#fff',
|
|
'color': '#ff0000'
|
|
});
|
|
$adds.css({
|
|
'position': 'absolute',
|
|
'cursor': 'pointer',
|
|
'background-color': '#fff',
|
|
'color': '#00ff00'
|
|
});
|
|
}
|
|
var fn_popover_update = eventHandler.modules.popover.update;
|
|
eventHandler.modules.popover.update = function ($popover, oStyle, isAirMode) {
|
|
fn_popover_update.call(this, $popover, oStyle, isAirMode);
|
|
if ((isAirMode ? $popover : $popover.parent()).find('.note-table').length) {
|
|
summernote_table_update(oStyle);
|
|
}
|
|
};
|
|
|
|
var fn_attach = eventHandler.attach;
|
|
eventHandler.attach = function (oLayoutInfo, options) {
|
|
var $editable = oLayoutInfo.editor().hasClass('note-editable') ? oLayoutInfo.editor() : oLayoutInfo.editor().find('.note-editable');
|
|
fn_attach.call(this, oLayoutInfo, options);
|
|
$editable.on("scroll", summernote_table_scroll);
|
|
};
|
|
var fn_detach = eventHandler.detach;
|
|
eventHandler.detach = function (oLayoutInfo, options) {
|
|
var $editable = oLayoutInfo.editor().hasClass('note-editable') ? oLayoutInfo.editor() : oLayoutInfo.editor().find('.note-editable');
|
|
fn_detach.call(this, oLayoutInfo, options);
|
|
$editable.off("scroll", summernote_table_scroll);
|
|
$('.o_table_handler').remove();
|
|
};
|
|
|
|
options.icons.image.image = "file-image-o";
|
|
$.summernote.lang['en-US'].image.image = "File / Image";
|
|
|
|
return $.summernote;
|
|
});
|