Added custom backspace/delete handling in Firefox
authorAlexander Ebert <ebert@woltlab.com>
Tue, 10 Jan 2017 19:21:33 +0000 (20:21 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Tue, 10 Jan 2017 19:21:33 +0000 (20:21 +0100)
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabKeydown.js

index a8c10a080995e64137863f25167882707388bd5e..987e46f49006df3684665562a3c9d67bc0379b9b 100644 (file)
@@ -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 `<span>`
-                                       this.core.editor().find('*[style]').not('span, img, #redactor-image-box, #redactor-image-editter').removeAttr('style');
-                                       
-                                       this.keydown.formatEmpty(e);
-                                       
-                                       // strip empty <kbd>
-                                       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 `<span>`
+                                       this.core.editor().find('*[style]').not('span, img, #redactor-image-box, #redactor-image-editter').removeAttr('style');
+                                       
+                                       this.keydown.formatEmpty(e);
+                                       
+                                       // strip empty <kbd>
+                                       var current = this.selection.current();
+                                       if (current.nodeName === 'KBD' && current.innerHTML.length === 0) {
+                                               elRemove(current);
+                                       }
+                                       
+                                       this.code.syncFire = true;
+                                       
+                               }, this), 1);
+                       }).bind(this);
                }
        }
 };