Duplicate Using backspace sometimes removes spaces

Affected version
2.2.5
I have seen another thread about this issue, but it seems it was never actually solved.


Every now and then, using the backspace results in the space before also being removed.

To view this content we will need your consent to set third party cookies.
For more detailed information, see our cookies page.
 
Can you reproduce the same issue on this forum and could you confirm which browser and OS versions you're using in the video?
 
Sorry, I posted the issue before receiving an answer from the affected user about tye version. He is using Chrome however. OS will follow.

I can't reproduce it. The issue was reported by a member.
 
This was enough information, thanks.

I'm no closer to figuring out why although to narrow it down slightly I think it's happening with pasted text in some cases or in other cases where there might be a stray line break.

Don't have an immediate solution but we might be able to produce a reduced test case. Though it has to be said, I can't reproduce this on the Froala demo so it's possible it's within our code.
 
I'm no closer to figuring out why although to narrow it down slightly I think it's happening with pasted text in some cases or in other cases where there might be a stray line break.
The editor has some rather unexpected behaviour if there are stray line breaks in the wrong spot after a copy & paste.

This bug report is about the editor buttons creating this issue; but I observed it more with copy & paste;

The original report is related to previously iOS bugs.
 
Yeah I rediscovered that one shortly after looking at this one. These are very likely essentially the same bug.
 
Yeah I rediscovered that one shortly after looking at this one. These are very likely essentially the same bug.
My favorite was finding you could make malformed html, and the editor would repeatedly create the same broken structure on trying to delete a space.
 
Last edited:
I've integrated that code. I've not done a deep amount testing but the cases pointed out here seem to be fine. Please speak out if the integration doesn't play well with your usage within your add-ons or has bits not relevant to these fixes or if the integration of the code would be better in different places.

Diff:
Index: js/xf/editor.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/xf/editor.js b/js/xf/editor.js
--- a/js/xf/editor.js    (revision Staged)
+++ b/js/xf/editor.js    (date 1631194909291)
@@ -902,7 +902,7 @@
                 $output = $children;
             }
 
-            return $output.html();
+            return XF.EditorHelpers.normalizeBrForEditor($output.html());
         },
 
         watchEditorHeight: function()
@@ -1320,6 +1320,8 @@
                 ed.html.insert(html);
                 ed.undo.saveStep();
                 XF.Element.initialize(ed.$el);
+
+                XF.EditorHelpers.normalizeAfterInsert(ed);
             }
 
             this.scrollToCursor();
@@ -1939,6 +1941,8 @@
             ed.selection.restore();
             ed.placeholder.hide();
             ed.undo.saveStep();
+
+            XF.EditorHelpers.normalizeAfterInsert(ed);
         },
 
         insertCode: function(ed, type, code)
@@ -1969,6 +1973,8 @@
             ed.undo.saveStep();
             ed.html.insert(output);
             ed.undo.saveStep();
+
+            XF.EditorHelpers.normalizeAfterInsert(ed);
         },
 
         insertSpoiler: function(ed, title)
@@ -1986,6 +1992,96 @@
             XF.EditorHelpers.wrapSelectionText(ed, open, '[/SPOILER]', true);
         },
 
+        normalizeBrForEditor: function (content)
+        {
+            var asString = typeof content === 'string',
+                $fragWrapper;
+
+            if (asString)
+            {
+                $fragWrapper = $('<div />').html(content);
+            }
+            else
+            {
+                $fragWrapper = content;
+            }
+
+            var checkNodeMatch = function ($node, elementType)
+            {
+                var node = $node.get(0);
+
+                return ($node.is(elementType)
+                    && node.className === ''
+                    && !node.hasAttribute('id')
+                    && !node.hasAttribute('style'));
+            };
+
+            // Workaround editor behaviour that a <br> should not be the first or last child of a <p> tag
+            // <p><br>...</p>; editor can delete too many lines
+            // <p>...<br></p>; editor can delete too few lines
+
+            $fragWrapper.children('p').each(function ()
+            {
+                if (this.childNodes.length !== 1)
+                {
+                    return;
+                }
+
+                var $firstChild = $(this.childNodes[0]);
+
+                if (checkNodeMatch($firstChild, 'span'))
+                {
+                    $(this).html($firstChild.html());
+                }
+            });
+
+            $fragWrapper.children('p').each(function()
+            {
+                if (this.childNodes.length <= 1)
+                {
+                    return;
+                }
+
+                var $firstChild = $(this.childNodes[0]);
+
+                if (checkNodeMatch($firstChild, 'br'))
+                {
+                    $(this).before($('<p>').append($firstChild));
+                }
+            });
+
+            $fragWrapper.children('p').each(function()
+            {
+                if (this.childNodes.length <= 1)
+                {
+                    return;
+                }
+
+                var $lastChild = $(this.childNodes[this.childNodes.length - 1]);
+
+                if (checkNodeMatch($lastChild, 'br'))
+                {
+                    $lastChild.remove();
+                }
+            });
+
+            return asString ? $fragWrapper.html() : $fragWrapper;
+        },
+
+        normalizeAfterInsert: function(ed)
+        {
+            var selected = ed.html.getSelected();
+
+            if (/<br>\s*<\/p>/.test(selected))
+            {
+                XF.EditorHelpers.normalizeBrForEditor(ed.$el);
+                // remove the last undo step and replace it with the corrected html version
+                ed.undo_index--;
+                ed.undo_stack.pop();
+                ed.undo.saveStep();
+            }
+        },
+
         isPreviewAvailable: function($textarea)
         {
             if (!$textarea.data('preview-url') && !$textarea.closest('form').data('preview-url'))
Interestingly that fix ☝️ doesn't have any impact here.

The best way to reproduce this is to paste a few words into the editor (even with the above you end up with a <br> at the end of the <p> tag and then backspace until you delete the last character before a space which will eat that space as well.
 
The best way to reproduce this is to paste a few words into the editor (even with the above you end up with a <br> at the end of the <p> tag and then backspace until you delete the last character before a space which will eat that space as well.
Good catch! It is definitely an edge case which was missed in the above br normalization code.

:edit: The existing normalization code just needs a little tweaking on when it is called;

The above edge-case only triggers when copy & pasting a sentence without newlines/paragraphs into;
a) an empty line in the editor
b) the very end of a line which has had text copied & pasted into
 
Last edited:
@Chris D Unfortunately the issue of the backspace acting weirdly, still exists.

2.2.7. We have not installed patch 1 yet.

To view this content we will need your consent to set third party cookies.
For more detailed information, see our cookies page.
 
Top Bottom