From 95aa9dc864289723c1a2c90932f06be7401ef174 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 21 Nov 2018 10:51:09 +0100 Subject: [PATCH] Fixes some edge cases on entity selection --- CHANGELOG.md | 2 ++ lib/api/DraftUtils.js | 18 +++++++--- lib/api/DraftUtils.test.js | 74 ++++++++++++++++++++++++++++---------- 3 files changed, 72 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ed06e35..82774341 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ - Stop unnecessarily calling `onSave` in the editor’s `onBlur` ([#173](https://github.com/springload/draftail/issues/173)). - Prevent crash when filtering pasted content whose last block is to be removed (e.g. unsupported image) ([#179](https://github.com/springload/draftail/issues/179)). +- Prevent crash in `DraftUtils.getEntitySelection`, when the provided entity key isn't valid (undefined, missing) ([#168](https://github.com/springload/draftail/pull/168)) +- Fix entity removal and editing not doing anything when the selection is backwards (right to left) ([#168](https://github.com/springload/draftail/pull/168)). ### Changed diff --git a/lib/api/DraftUtils.js b/lib/api/DraftUtils.js index 22b2b9f6..d3a02c62 100644 --- a/lib/api/DraftUtils.js +++ b/lib/api/DraftUtils.js @@ -61,9 +61,15 @@ export default { /** * Creates a selection on a given entity in the currently selected block. + * Returns the current selection if no entity key is provided, or if the entity could not be found. */ getEntitySelection(editorState, entityKey) { - const selectionState = editorState.getSelection(); + const selection = editorState.getSelection(); + + if (!entityKey) { + return selection; + } + const block = this.getSelectedBlock(editorState); let entityRange; // https://github.com/jpuri/draftjs-utils/blob/e81c0ae19c3b0fdef7e0c1b70d924398956be126/js/inline.js#L111 @@ -77,9 +83,13 @@ export default { }, ); - return selectionState.merge({ - anchorOffset: entityRange.start, - focusOffset: entityRange.end, + if (!entityRange) { + return selection; + } + + return selection.merge({ + anchorOffset: selection.isBackward ? entityRange.end : entityRange.start, + focusOffset: selection.isBackward ? entityRange.start : entityRange.end, }); }, diff --git a/lib/api/DraftUtils.test.js b/lib/api/DraftUtils.test.js index 84b430d0..2350c13b 100644 --- a/lib/api/DraftUtils.test.js +++ b/lib/api/DraftUtils.test.js @@ -152,33 +152,71 @@ describe("DraftUtils", () => { describe("#getEntitySelection", () => { it("works", () => { - const contentBlocks = convertFromHTML("

aaaaaaaaaa

"); - const contentState = ContentState.createFromBlockArray(contentBlocks); - let editorState = EditorState.createWithContent(contentState); - const updatedSelection = editorState.getSelection().merge({ + const content = ContentState.createFromText("hello, world"); + let editorState = EditorState.createWithContent(content); + const selection = editorState.getSelection().merge({ anchorOffset: 0, - focusOffset: 5, + focusOffset: 4, + }); + + const contentStateWithEntity = content.createEntity("LINK", "MUTABLE", { + url: "www.testing.com", }); - const contentStateWithEntity = contentState.createEntity( - "LINK", - "MUTABLE", - { url: "www.testing.com" }, - ); const entityKey = contentStateWithEntity.getLastCreatedEntityKey(); - editorState = RichUtils.toggleLink( + editorState = RichUtils.toggleLink(editorState, selection, entityKey); + editorState = EditorState.forceSelection( editorState, - updatedSelection, - entityKey, + selection.merge({ focusOffset: 0 }), ); - const entitySelection = DraftUtils.getEntitySelection( + expect( + DraftUtils.getEntitySelection(editorState, entityKey).toJS(), + ).toMatchObject({ + anchorOffset: 0, + focusOffset: 4, + }); + }); + + it("supports backwards selections (#168)", () => { + const content = ContentState.createFromText("hello, world"); + let editorState = EditorState.createWithContent(content); + const selection = editorState.getSelection().merge({ + anchorOffset: 5, + focusOffset: 0, + isBackward: true, + }); + const contentStateWithEntity = content.createEntity("LINK", "MUTABLE", { + url: "www.testing.com", + }); + const entityKey = contentStateWithEntity.getLastCreatedEntityKey(); + editorState = RichUtils.toggleLink(editorState, selection, entityKey); + editorState = EditorState.forceSelection( editorState, - entityKey, + selection.merge({ anchorOffset: 0 }), ); - expect(entitySelection.toJS()).toMatchObject({ - anchorOffset: 0, - focusOffset: 5, + expect( + DraftUtils.getEntitySelection(editorState, entityKey).toJS(), + ).toMatchObject({ + anchorOffset: 5, + focusOffset: 0, + isBackward: true, }); }); + + it("entity not found should not change selection (#168)", () => { + const content = ContentState.createFromText("hello, world"); + const editorState = EditorState.createWithContent(content); + expect(DraftUtils.getEntitySelection(editorState, "1")).toBe( + editorState.getSelection(), + ); + }); + + it("missing entity key should not change selection (#168)", () => { + const content = ContentState.createFromText("hello, world"); + const editorState = EditorState.createWithContent(content); + expect(DraftUtils.getEntitySelection(editorState, null)).toBe( + editorState.getSelection(), + ); + }); }); describe("#updateBlockEntity", () => {