From a8dd26674de74606b9594d31adba3fac02b1743f Mon Sep 17 00:00:00 2001 From: Chupurnov Valeriy Date: Wed, 7 Feb 2018 10:14:38 +0500 Subject: [PATCH] Fix a lot of bugs with backspace keypressing inside inline elements --- src/modules/Dom.ts | 5 ++++- src/modules/Selection.ts | 25 ++++++++++----------- src/plugins/backspace.ts | 47 +++++++++++++++++++++++++++++++++++---- test/tests/enterTest.js | 25 ++++++++++++++++++++- test/tests/pluginsTest.js | 2 +- 5 files changed, 84 insertions(+), 20 deletions(-) diff --git a/src/modules/Dom.ts b/src/modules/Dom.ts index 699d38a80..d02e70df5 100644 --- a/src/modules/Dom.ts +++ b/src/modules/Dom.ts @@ -5,7 +5,7 @@ */ import * as consts from '../constants'; -import {each, trim} from './Helpers' +import {css, each, trim} from './Helpers' import {Jodit} from "../Jodit"; export class Dom { @@ -210,6 +210,9 @@ export class Dom { return (!!node && typeof node.nodeName === 'string' && consts.IS_BLOCK.test(node.nodeName)); } + static isInlineBlock(node: Node | null): boolean { + return !!node && node.nodeType === Node.ELEMENT_NODE && ['inline', 'inline-block'].indexOf(css(node, 'display').toString()) !== -1; + } /** * It's block and it can be split * diff --git a/src/modules/Selection.ts b/src/modules/Selection.ts index ed40e7dc3..b48369d8d 100644 --- a/src/modules/Selection.ts +++ b/src/modules/Selection.ts @@ -547,10 +547,7 @@ export class Select extends Component{ } range.collapse(false); - sel.removeAllRanges(); - sel.addRange(range); - - this.jodit.events.fire('changeSelection'); + this.selectRange(range); return fakeNode; } @@ -745,10 +742,7 @@ export class Select extends Component{ } range.collapse(true); - sel.removeAllRanges(); - sel.addRange(range); - - this.jodit.events.fire('changeSelection'); + this.selectRange(range); return fakeNode; } @@ -781,12 +775,17 @@ export class Select extends Component{ start = start[inStart ? 'firstChild' : 'lastChild']; } while (start); + if (last === node && node.nodeType !== Node.TEXT_NODE) { + const fakeNode: Text = this.jodit.editorDocument.createTextNode(consts.INVISIBLE_SPACE); + node.appendChild(fakeNode); + last = fakeNode; + } + range.selectNodeContents(start || last); range.collapse(inStart); - sel.removeAllRanges(); - sel.addRange(range); + this.selectRange(range); - this.jodit.events.fire('changeSelection'); + return last; } selectRange(range: Range) { @@ -814,8 +813,8 @@ export class Select extends Component{ range: Range = this.jodit.editorDocument.createRange(); range[inward ? 'selectNodeContents' : 'selectNode'](node); - sel.removeAllRanges(); - sel.addRange(range); + + this.selectRange(range); } diff --git a/src/plugins/backspace.ts b/src/plugins/backspace.ts index 799903850..49d7bf4d6 100644 --- a/src/plugins/backspace.ts +++ b/src/plugins/backspace.ts @@ -68,8 +68,21 @@ export function backspace(editor: Jodit) { range.setStart(box.node, startOffset); range.collapse(true); - - let nextElement: Node | null = toLeft ? box.node.previousSibling : box.node.nextSibling; + editor.selection.selectRange(range); + + let prevElement: Node | null = box.node; + let nextElement: Node | null = null; + + do { + if (prevElement) { + nextElement = toLeft ? prevElement.previousSibling : prevElement.nextSibling; + if (!nextElement && prevElement.parentNode && prevElement.parentNode !== editor.editor && Dom.isInlineBlock(prevElement.parentNode)) { + prevElement = prevElement.parentNode; + } else { + break; + } + } + } while(!nextElement); if (value.length) { if (toLeft) { @@ -80,6 +93,7 @@ export function backspace(editor: Jodit) { } range.setStart(box.node, startOffset - 1); range.collapse(true); + editor.selection.selectRange(range); return false; } } else { @@ -90,12 +104,14 @@ export function backspace(editor: Jodit) { } range.setStart(box.node, startOffset); range.collapse(true); + editor.selection.selectRange(range); return false; } } } else { range.setStartBefore(box.node); range.collapse(true); + editor.selection.selectRange(range); box.node && box.node.parentNode && box.node.parentNode.removeChild(box.node); @@ -103,9 +119,10 @@ export function backspace(editor: Jodit) { } if (nextElement) { - if (nextElement.nodeType === Node.ELEMENT_NODE && ['inline', 'inline-block'].indexOf(css(nextElement, 'display').toString()) !== -1) { + if (Dom.isInlineBlock(nextElement)) { nextElement = toLeft ? nextElement.lastChild : nextElement.firstChild; } + if (nextElement && nextElement.nodeType === Node.TEXT_NODE) { box.node = nextElement; return removeChar(box, toLeft, range); @@ -118,6 +135,10 @@ export function backspace(editor: Jodit) { if (event.which === consts.KEY_BACKSPACE || event.keyCode === consts.KEY_DELETE) { const toLeft: boolean = event.which === consts.KEY_BACKSPACE; + if (!editor.selection.isFocused()) { + !editor.selection.focus(); + } + if (!editor.selection.isCollapsed()) { editor.execCommand('Delete'); return false; @@ -129,17 +150,28 @@ export function backspace(editor: Jodit) { if (!range) { return false; } + const fakeNode: Node = editor.ownerDocument.createTextNode(consts.INVISIBLE_SPACE); + const marker: HTMLElement = editor.editorDocument.createElement('span'); + try { range.insertNode(fakeNode); + if (!Dom.isOrContains(editor.editor, fakeNode)) { + return false; + } let container: HTMLElement | null = Dom.up(fakeNode, Dom.isBlock, editor.editor); let workElement: Node | null; workElement = toLeft ? fakeNode.previousSibling : fakeNode.nextSibling; + while (workElement && Dom.isInlineBlock(workElement) && (!toLeft ? workElement.firstChild : workElement.lastChild)) { + workElement = !toLeft ? workElement.firstChild : workElement.lastChild; + } + if (workElement) { const box = {node: workElement}; + if (removeChar(box, toLeft, range) === false) { return false; } @@ -174,7 +206,8 @@ export function backspace(editor: Jodit) { box.parentNode && box.parentNode.insertBefore(prevBox, box); } - prevBox && editor.selection.setCursorIn(prevBox, !toLeft); + + prevBox && editor.selection.setCursorIn(prevBox, !toLeft) && editor.selection.insertNode(marker, false); if (container) { removeEmptyBlocks(container); @@ -206,9 +239,15 @@ export function backspace(editor: Jodit) { removeEmptyBlocks(container); + + if (marker) { + editor.selection.setCursorBefore(marker); + } + return false; } } finally { + marker.parentNode && marker.parentNode.removeChild(marker); fakeNode.parentNode && fakeNode.parentNode.removeChild(fakeNode); } diff --git a/test/tests/enterTest.js b/test/tests/enterTest.js index 0a56c0914..7a57d4298 100644 --- a/test/tests/enterTest.js +++ b/test/tests/enterTest.js @@ -24,7 +24,30 @@ describe('Enter behavior Jodit Editor Tests', function() { '').to.be.equal(editor.getEditorValue()); }); }); + describe('inside some inline element', function () { + describe('in the start', function () { + it('Should move cursor befoer this element and delete char', function () { + var editor = new Jodit(appendTestArea()) + editor.setEditorValue('testopst'); + + var sel = editor.editorWindow.getSelection(), + range = editor.editorDocument.createRange(); + + range.selectNodeContents(editor.editor.querySelector('strong')); + range.collapse(true); + sel.removeAllRanges(); + sel.addRange(range); + simulateEvent('keydown', Jodit.KEY_BACKSPACE, editor.editor); + + expect('tstopst').to.be.equal(editor.getEditorValue()); + + + editor.selection.insertNode(editor.editorDocument.createTextNode(' a ')) + expect('t a stopst').to.be.equal(editor.getEditorValue()); + }) + }); + }); describe('inside empty P', function () { it('Should remove empty tag and set cursor in previous element', function () { var editor = new Jodit(appendTestArea()) @@ -87,7 +110,7 @@ describe('Enter behavior Jodit Editor Tests', function() { var sel = editor.editorWindow.getSelection(), range = editor.editorDocument.createRange(); - range.setStart(editor.editor.lastChild, 0); + range.setStartAfter(editor.editor.querySelector('span')); range.collapse(true); sel.removeAllRanges(); sel.addRange(range); diff --git a/test/tests/pluginsTest.js b/test/tests/pluginsTest.js index d96b656dd..26580201c 100644 --- a/test/tests/pluginsTest.js +++ b/test/tests/pluginsTest.js @@ -273,7 +273,7 @@ describe('Test plugins', function () { editor.selection.insertHTML('stop'); - expect('

stop

').to.be.equal(editor.getEditorValue()); + expect('

stop

').to.be.equal(sortAtrtibutes(editor.getEditorValue())); }); }); });