From bb6796ac500189d8922dc2bb8a658eaf287b665e Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Tue, 10 Jan 2017 20:21:33 +0100 Subject: [PATCH] Added custom backspace/delete handling in Firefox --- .../redactor2/plugins/WoltLabKeydown.js | 266 ++++++++++++++++-- 1 file changed, 243 insertions(+), 23 deletions(-) diff --git a/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabKeydown.js b/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabKeydown.js index a8c10a0809..987e46f490 100644 --- a/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabKeydown.js +++ b/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabKeydown.js @@ -83,29 +83,6 @@ $.Redactor.prototype.WoltLabKeydown = function() { } }).bind(this); - this.keydown.onBackspaceAndDeleteAfter = (function (e) { - // remove style tag - setTimeout($.proxy(function() - { - this.code.syncFire = false; - this.keydown.removeEmptyLists(); - - // WoltLab modification: allow style tag on `` - this.core.editor().find('*[style]').not('span, img, #redactor-image-box, #redactor-image-editter').removeAttr('style'); - - this.keydown.formatEmpty(e); - - // strip empty - var current = this.selection.current(); - if (current.nodeName === 'KBD' && current.innerHTML.length === 0) { - elRemove(current); - } - - this.code.syncFire = true; - - }, this), 1); - }).bind(this); - var mpOnEnter = (function(e) { var stop = this.core.callback('enter', e); if (stop === false) { @@ -365,6 +342,7 @@ $.Redactor.prototype.WoltLabKeydown = function() { } }).bind(this)); + this.WoltLabKeydown._handleBackspaceAndDelete(); }, register: function (tag) { @@ -381,6 +359,248 @@ $.Redactor.prototype.WoltLabKeydown = function() { } return tags; + }, + + _handleBackspaceAndDelete: function () { + var isEmpty = function(element) { + return (elBySel('img', element) === null && element.textContent.replace(/\u200B/g, '').trim() === ''); + }; + + var firefoxHandleBackspace = (function(e) { + var parent; + var block = this.selection.block(); + if (block.nodeName.indexOf('-') !== -1 && isEmpty(block)) { + // backspacing an entire block + parent = block.parentNode; + parent.insertBefore(this.marker.get(), block.nextSibling); + + elRemove(block); + + this.selection.restore(); + } + else { + parent = (block && block.nodeName === 'P') ? block.parentNode : null; + if (parent && parent.nodeName.indexOf('-') !== -1) { + var orgRange = window.getSelection().getRangeAt(0); + + // check if there is anything in front of the caret + var range = document.createRange(); + range.setStartBefore(block); + range.setEnd(orgRange.startContainer, orgRange.startOffset); + + var fragment = range.cloneContents(); + var div = elCreate('div'); + div.appendChild(fragment); + + // caret is at start + if (isEmpty(div)) { + // prevent Firefox from giving the DOM a good beating + e.preventDefault(); + + var sibling = block.previousElementSibling; + // can be `true` to remove, `false` to merge and `null` to do nothing + var removeSibling = null; + if (sibling) { + removeSibling = (isEmpty(sibling)); + } + else { + parent = block; + while (parent = parent.parentNode) { + if (parent === this.$editor[0]) { + break; + } + + sibling = parent.previousElementSibling; + if (sibling) { + // setting to false triggers the merge + removeSibling = false; + break; + } + } + } + + if (removeSibling) { + elRemove(sibling); + } + else if (removeSibling !== null) { + var oldParent = block.parentNode; + + // merge blocks + if (sibling.nodeName === 'P') { + sibling.appendChild(this.marker.get()); + + while (block.childNodes.length) { + sibling.appendChild(block.childNodes[0]); + } + + elRemove(block); + this.selection.restore(); + } + else { + sibling.appendChild(block); + + block.insertBefore(this.marker.get(), block.firstChild); + this.selection.restore(); + } + + // check if `parent` is now completely empty + if (isEmpty(oldParent)) { + elRemove(oldParent); + } + } + else if (removeSibling === null) { + // check if the parent is empty and the user wants to remove the parent + parent = block.parentNode; + if (isEmpty(parent)) { + elRemove(parent); + } + } + } + } + } + }).bind(this); + + var firefoxHandleDelete = (function(e) { + var parent; + var block = this.selection.block(); + if (block.nodeName.indexOf('-') !== -1 && isEmpty(block)) { + // deleting an entire block + parent = block.parentNode; + parent.insertBefore(this.marker.get(), block.nextSibling); + + elRemove(block); + + this.selection.restore(); + } + else { + parent = (block && block.nodeName === 'P') ? block.parentNode : null; + if (parent && parent.nodeName.indexOf('-') !== -1) { + var orgRange = window.getSelection().getRangeAt(0); + + // check if there is anything after the caret + var range = document.createRange(); + range.setStart(orgRange.startContainer, orgRange.startOffset); + range.setEndAfter(block); + + var fragment = range.cloneContents(); + var div = elCreate('div'); + div.appendChild(fragment); + + // caret is at end + if (isEmpty(div)) { + // prevent Firefox from giving the DOM a good beating + e.preventDefault(); + + var sibling = block.nextElementSibling; + // can be `true` to remove, `false` to merge and `null` to do nothing + var removeSibling = null; + if (sibling) { + removeSibling = (isEmpty(sibling)); + } + else { + parent = block; + while (parent = parent.parentNode) { + if (parent === this.$editor[0]) { + break; + } + + sibling = parent.nextElementSibling; + if (sibling) { + // setting to false triggers the merge + removeSibling = false; + break; + } + } + } + + if (removeSibling) { + elRemove(sibling); + } + else if (removeSibling !== null) { + var oldParent = sibling.parentNode; + + // merge blocks + if (sibling.nodeName === 'P') { + while (sibling.childNodes.length) { + block.appendChild(sibling.childNodes[0]); + } + + elRemove(sibling); + } + else { + block.appendChild(this.marker.get()); + + parent = block.parentNode; + if (sibling.nodeName.indexOf('-') !== -1) { + var content = sibling.firstElementChild; + if (content && content.nodeName === 'P') { + while (content.childNodes.length) { + block.appendChild(content.childNodes[0]); + } + + sibling.removeChild(content); + + if (isEmpty(sibling)) { + elRemove(sibling); + } + } + } + else { + parent.insertBefore(sibling, block.nextSibling); + } + + this.selection.restore(); + } + + // check if `parent` is now completely empty + if (isEmpty(oldParent)) { + elRemove(oldParent); + } + } + else if (removeSibling === null) { + // check if the parent is empty and the user wants to remove the parent + parent = block.parentNode; + if (isEmpty(parent)) { + elRemove(parent); + } + } + } + } + } + }).bind(this); + + this.keydown.onBackspaceAndDeleteAfter = (function (e) { + //noinspection JSValidateTypes + if (this.detect.isFirefox() && this.selection.isCollapsed()) { + if (e.which === this.keyCode.BACKSPACE) { + firefoxHandleBackspace(e); + } + else if (e.which === this.keyCode.DELETE) { + firefoxHandleDelete(e); + } + } + + // remove style tag + setTimeout($.proxy(function() + { + this.code.syncFire = false; + this.keydown.removeEmptyLists(); + + // WoltLab modification: allow style tag on `` + this.core.editor().find('*[style]').not('span, img, #redactor-image-box, #redactor-image-editter').removeAttr('style'); + + this.keydown.formatEmpty(e); + + // strip empty + var current = this.selection.current(); + if (current.nodeName === 'KBD' && current.innerHTML.length === 0) { + elRemove(current); + } + + this.code.syncFire = true; + + }, this), 1); + }).bind(this); } } }; -- 2.20.1