Skip to content

Commit

Permalink
Fix merge block elements on delete transfers sometimes only first tex…
Browse files Browse the repository at this point in the history
…t node

Make conserve actions testable
  • Loading branch information
Oliver Pulges committed Dec 15, 2015
1 parent 735995c commit 35121df
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 147 deletions.
300 changes: 154 additions & 146 deletions src/views/composer.observe.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,155 +18,184 @@
"73": "italic", // I
"85": "underline" // U
};

// Adds multiple eventlisteners to target, bound to one callback
// TODO: If needed elsewhere make it part of wysihtml5.dom or sth
var addListeners = function (target, events, callback) {
for(var i = 0, max = events.length; i < max; i++) {
target.addEventListener(events[i], callback, false);
}
};

// Removes multiple eventlisteners from target, bound to one callback
// TODO: If needed elsewhere make it part of wysihtml5.dom or sth
var removeListeners = function (target, events, callback) {
for(var i = 0, max = events.length; i < max; i++) {
target.removeEventListener(events[i], callback, false);
}
};

// Override for giving user ability to delete last line break in table cell
var fixLastBrDeletionInTable = function(composer, force) {
if (composer.selection.caretIsLastInSelection()) {
var sel = composer.selection.getSelection(),
aNode = sel.anchorNode;
if (aNode && aNode.nodeType === 1 && (wysihtml5.dom.getParentElement(aNode, {query: 'td, th'}, false, composer.element) || force)) {
var nextNode = aNode.childNodes[sel.anchorOffset];
if (nextNode && nextNode.nodeType === 1 & nextNode.nodeName === "BR") {
nextNode.parentNode.removeChild(nextNode);
return true;
}

var actions = {

// Adds multiple eventlisteners to target, bound to one callback
// TODO: If needed elsewhere make it part of wysihtml5.dom or sth
addListeners: function (target, events, callback) {
for(var i = 0, max = events.length; i < max; i++) {
target.addEventListener(events[i], callback, false);
}
}
return false;
};
},

// If found an uneditable before caret then notify it before deletion
var handleUneditableDeletion = function(composer) {
var before = composer.selection.getBeforeSelection(true);
if (before && (before.type === "element" || before.type === "leafnode") && before.node.nodeType === 1 && before.node.classList.contains(composer.config.classNames.uneditableContainer)) {
if (fixLastBrDeletionInTable(composer, true)) {
return true;
// Removes multiple eventlisteners from target, bound to one callback
// TODO: If needed elsewhere make it part of wysihtml5.dom or sth
removeListeners: function (target, events, callback) {
for(var i = 0, max = events.length; i < max; i++) {
target.removeEventListener(events[i], callback, false);
}
try {
var ev = new CustomEvent("wysihtml5:uneditable:delete", {bubbles: true, cancelable: false});
before.node.dispatchEvent(ev);
} catch (err) {}
before.node.parentNode.removeChild(before.node);
return true;
}
return false;
};

// Deletion with caret in the beginning of headings and other block elvel elements needs special attention
// Not allways does it concate text to previous block node correctly (browsers do unexpected miracles here especially webkit)
var fixDeleteInTheBeginningOfBlock = function(composer) {
var selection = composer.selection,
prevNode = selection.getPreviousNode();

if (selection.caretIsFirstInSelection() &&
prevNode &&
prevNode.nodeType === 1 &&
(/block/).test(composer.win.getComputedStyle(prevNode).display) &&
!domNode(prevNode).test({
query: "ol, ul, table, tr, dl"
})
) {
if ((/^\s*$/).test(prevNode.textContent || prevNode.innerText)) {
// If heading is empty remove the heading node
prevNode.parentNode.removeChild(prevNode);
return true;
} else {
if (prevNode.lastChild) {
var selNode = prevNode.lastChild,
selectedNode = selection.getSelectedNode(),
commonAncestorNode = domNode(prevNode).commonAncestor(selectedNode, composer.element);
curNode = selectedNode.nodeType === 3 ? selectedNode : wysihtml5.dom.getParentElement(selectedNode, {
query: "h1, h2, h3, h4, h5, h6, p, pre, div, blockquote"
}, false, commonAncestorNode || composer.element);

if (curNode) {
domNode(curNode).transferContentTo(prevNode, true);
selection.setAfter(selNode);
},

// Override for giving user ability to delete last line break in table cell
fixLastBrDeletionInTable: function(composer, force) {
if (composer.selection.caretIsLastInSelection()) {
var sel = composer.selection.getSelection(),
aNode = sel.anchorNode;
if (aNode && aNode.nodeType === 1 && (wysihtml5.dom.getParentElement(aNode, {query: 'td, th'}, false, composer.element) || force)) {
var nextNode = aNode.childNodes[sel.anchorOffset];
if (nextNode && nextNode.nodeType === 1 & nextNode.nodeName === "BR") {
nextNode.parentNode.removeChild(nextNode);
return true;
}
}
}
}
return false;
};

/* In IE when deleting with caret at the begining of LI, list gets broken into half instead of merging the LI with previous */
/* This does not match other browsers an is less intuitive from UI standpoint, thus has to be fixed */
var fixDeleteInTheBeginningOfLi = function(composer) {
if (wysihtml5.browser.hasLiDeletingProblem()) {
var selection = composer.selection.getSelection(),
aNode = selection.anchorNode,
listNode, prevNode, firstNode,
isInBeginnig = composer.selection.caretIsFirstInSelection();

// Fix caret at the beginnig of first textNode in LI
if (aNode.nodeType === 3 && selection.anchorOffset === 0 && aNode === aNode.parentNode.firstChild) {
aNode = aNode.parentNode;
isInBeginnig = true;
return false;
},

// If found an uneditable before caret then notify it before deletion
handleUneditableDeletion: function(composer) {
var before = composer.selection.getBeforeSelection(true);
if (before && (before.type === "element" || before.type === "leafnode") && before.node.nodeType === 1 && before.node.classList.contains(composer.config.classNames.uneditableContainer)) {
if (actions.fixLastBrDeletionInTable(composer, true)) {
return true;
}
try {
var ev = new CustomEvent("wysihtml5:uneditable:delete", {bubbles: true, cancelable: false});
before.node.dispatchEvent(ev);
} catch (err) {}
before.node.parentNode.removeChild(before.node);
return true;
}

if (isInBeginnig && aNode && aNode.nodeType === 1 && aNode.nodeName === "LI") {
prevNode = domNode(aNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
if (!prevNode && aNode.parentNode && (aNode.parentNode.nodeName === "UL" || aNode.parentNode.nodeName === "OL")) {
prevNode = domNode(aNode.parentNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
return false;
},

// Deletion with caret in the beginning of headings and other block elvel elements needs special attention
// Not allways does it concate text to previous block node correctly (browsers do unexpected miracles here especially webkit)
fixDeleteInTheBeginningOfBlock: function(composer) {
var selection = composer.selection,
prevNode = selection.getPreviousNode();

if (selection.caretIsFirstInSelection() &&
prevNode &&
prevNode.nodeType === 1 &&
(/block/).test(composer.win.getComputedStyle(prevNode).display) &&
!domNode(prevNode).test({
query: "ol, ul, table, tr, dl"
})
) {
if ((/^\s*$/).test(prevNode.textContent || prevNode.innerText)) {
// If heading is empty remove the heading node
prevNode.parentNode.removeChild(prevNode);
return true;
} else {
if (prevNode.lastChild) {
var selNode = prevNode.lastChild,
selectedNode = selection.getSelectedNode(),
commonAncestorNode = domNode(prevNode).commonAncestor(selectedNode, composer.element),
curNode = wysihtml5.dom.getParentElement(selectedNode, {
query: "h1, h2, h3, h4, h5, h6, p, pre, div, blockquote"
}, false, commonAncestorNode || composer.element);

if (curNode) {
domNode(curNode).transferContentTo(prevNode, true);
selection.setAfter(selNode);
return true;
}
}
}
if (prevNode) {
firstNode = aNode.firstChild;
domNode(aNode).transferContentTo(prevNode, true);
if (firstNode) {
composer.selection.setBefore(firstNode);
} else if (prevNode) {
if (prevNode.nodeType === 1) {
if (prevNode.lastChild) {
composer.selection.setAfter(prevNode.lastChild);
}
return false;
},

/* In IE when deleting with caret at the begining of LI, list gets broken into half instead of merging the LI with previous */
/* This does not match other browsers an is less intuitive from UI standpoint, thus has to be fixed */
fixDeleteInTheBeginningOfLi: function(composer) {
if (wysihtml5.browser.hasLiDeletingProblem()) {
var selection = composer.selection.getSelection(),
aNode = selection.anchorNode,
listNode, prevNode, firstNode,
isInBeginnig = composer.selection.caretIsFirstInSelection();

// Fix caret at the beginnig of first textNode in LI
if (aNode.nodeType === 3 && selection.anchorOffset === 0 && aNode === aNode.parentNode.firstChild) {
aNode = aNode.parentNode;
isInBeginnig = true;
}

if (isInBeginnig && aNode && aNode.nodeType === 1 && aNode.nodeName === "LI") {
prevNode = domNode(aNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
if (!prevNode && aNode.parentNode && (aNode.parentNode.nodeName === "UL" || aNode.parentNode.nodeName === "OL")) {
prevNode = domNode(aNode.parentNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
}
if (prevNode) {
firstNode = aNode.firstChild;
domNode(aNode).transferContentTo(prevNode, true);
if (firstNode) {
composer.selection.setBefore(firstNode);
} else if (prevNode) {
if (prevNode.nodeType === 1) {
if (prevNode.lastChild) {
composer.selection.setAfter(prevNode.lastChild);
} else {
composer.selection.selectNode(prevNode);
}
} else {
composer.selection.selectNode(prevNode);
composer.selection.setAfter(prevNode);
}
} else {
composer.selection.setAfter(prevNode);
}
return true;
}
return true;
}
}
return false;
},

// Table management
// If present enableObjectResizing and enableInlineTableEditing command should be called with false to prevent native table handlers
initTableHandling: function() {
var hideHandlers = function() {
window.removeEventListener('load', hideHandlers);
this.doc.execCommand("enableObjectResizing", false, "false");
this.doc.execCommand("enableInlineTableEditing", false, "false");
}.bind(this),
iframeInitiator = (function() {
hideHandlers.call(this);
actions.removeListeners(this.sandbox.getIframe(), ["focus", "mouseup", "mouseover"], iframeInitiator);
}).bind(this);

if( this.doc.execCommand &&
wysihtml5.browser.supportsCommand(this.doc, "enableObjectResizing") &&
wysihtml5.browser.supportsCommand(this.doc, "enableInlineTableEditing"))
{
if (this.sandbox.getIframe) {
actions.addListeners(this.sandbox.getIframe(), ["focus", "mouseup", "mouseover"], iframeInitiator);
} else {
window.addEventListener('load', hideHandlers);
}
}
this.tableSelection = wysihtml5.quirks.tableCellsSelection(this.element, this.parent);
}
return false;
}
};

var handleDeleteKeyPress = function(event, composer) {
var selection = composer.selection,
element = composer.element;

if (selection.isCollapsed()) {
if (handleUneditableDeletion(composer)) {
if (actions.handleUneditableDeletion(composer)) {
event.preventDefault();
return;
}
if (fixDeleteInTheBeginningOfLi(composer)) {
if (actions.fixDeleteInTheBeginningOfLi(composer)) {
event.preventDefault();
return;
}
if (fixDeleteInTheBeginningOfBlock(composer)) {
if (actions.fixDeleteInTheBeginningOfBlock(composer)) {
event.preventDefault();
return;
}
if (fixLastBrDeletionInTable(composer)) {
if (actions.fixLastBrDeletionInTable(composer)) {
event.preventDefault();
return;
}
Expand Down Expand Up @@ -425,31 +454,10 @@
}).bind(this), 0);
};

// Table management
// If present enableObjectResizing and enableInlineTableEditing command should be called with false to prevent native table handlers
var initTableHandling = function () {
var hideHandlers = function () {
window.removeEventListener('load', hideHandlers);
this.doc.execCommand("enableObjectResizing", false, "false");
this.doc.execCommand("enableInlineTableEditing", false, "false");
}.bind(this),
iframeInitiator = (function() {
hideHandlers.call(this);
removeListeners(this.sandbox.getIframe(), ["focus", "mouseup", "mouseover"], iframeInitiator);
}).bind(this);

if( this.doc.execCommand &&
wysihtml5.browser.supportsCommand(this.doc, "enableObjectResizing") &&
wysihtml5.browser.supportsCommand(this.doc, "enableInlineTableEditing"))
{
if (this.sandbox.getIframe) {
addListeners(this.sandbox.getIframe(), ["focus", "mouseup", "mouseover"], iframeInitiator);
} else {
window.addEventListener('load', hideHandlers);
}
}
this.tableSelection = wysihtml5.quirks.tableCellsSelection(this.element, this.parent);
};


// Testing requires actions to be accessible from out of scope
wysihtml5.views.Composer.prototype.observeActions = actions;

wysihtml5.views.Composer.prototype.observe = function() {
var that = this,
Expand All @@ -475,14 +483,14 @@
// --------- User interactions --
if (this.config.handleTables) {
// If handleTables option is true, table handling functions are bound
initTableHandling.call(this);
actions.initTableHandling.call(this);
}

addListeners(focusBlurElement, ["drop", "paste", "mouseup", "focus", "keyup"], handleUserInteraction.bind(this));
actions.addListeners(focusBlurElement, ["drop", "paste", "mouseup", "focus", "keyup"], handleUserInteraction.bind(this));
focusBlurElement.addEventListener("focus", handleFocus.bind(this), false);
focusBlurElement.addEventListener("blur", handleBlur.bind(this), false);

addListeners(this.element, ["drop", "paste", "beforepaste"], handlePaste.bind(this), false);
actions.addListeners(this.element, ["drop", "paste", "beforepaste"], handlePaste.bind(this), false);
this.element.addEventListener("copy", handleCopy.bind(this), false);
this.element.addEventListener("mousedown", handleMouseDown.bind(this), false);
this.element.addEventListener("click", handleClick.bind(this), false);
Expand Down
3 changes: 2 additions & 1 deletion test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,8 @@
'<script src="editor_contenteditablemode_test.js"><\/script>' +
'<script src="editor_commands_test.js"><\/script>' +
'<script src="commands/formatInline_test.js"><\/script>' +
'<script src="commands/formatBlock_test.js"><\/script>'
'<script src="commands/formatBlock_test.js"><\/script>' +
'<script src="views/composer_test.js"><\/script>'
);
}
</script>
Expand Down
Loading

0 comments on commit 35121df

Please sign in to comment.