From 519a6de935c246d222393115dafcee3a34779172 Mon Sep 17 00:00:00 2001 From: Zaydek Michels-Gualtieri Date: Thu, 30 Jul 2020 04:13:59 +0900 Subject: [PATCH] Fixed several bugs with getRangeTypes --- src/Editor/useEditor/actions.js | 22 +++++----- src/Editor/useEditor/createIndexAtOffset.js | 3 +- src/Editor/useEditor/getIndex.js | 29 ++++++++++++++ src/Editor/useEditor/getIndex.test.js | 25 ++++++++++++ src/Editor/useEditor/getIndexAtOffset.js | 25 ------------ src/Editor/useEditor/getIndexAtOffset.test.js | 25 ------------ src/Editor/useEditor/getRangeTypes.js | 40 +++++++------------ 7 files changed, 81 insertions(+), 88 deletions(-) create mode 100644 src/Editor/useEditor/getIndex.js create mode 100644 src/Editor/useEditor/getIndex.test.js delete mode 100644 src/Editor/useEditor/getIndexAtOffset.js delete mode 100644 src/Editor/useEditor/getIndexAtOffset.test.js diff --git a/src/Editor/useEditor/actions.js b/src/Editor/useEditor/actions.js index fed2f80..70e7620 100644 --- a/src/Editor/useEditor/actions.js +++ b/src/Editor/useEditor/actions.js @@ -2,7 +2,7 @@ import addOrRemoveTypesOnSelection from "./addOrRemoveTypesOnSelection" import deleteOnSelection from "./deleteOnSelection" import extendRangeLTR from "./extendRangeLTR" import extendRangeRTL from "./extendRangeRTL" -import getIndexAtOffset from "./getIndexAtOffset" +import getIndex from "./getIndex" import getRangeTypes from "./getRangeTypes" import getShorthandVars from "./getShorthandVars" import insertHardParagraphAtCollapsed from "./insertHardParagraphAtCollapsed" @@ -47,17 +47,17 @@ export function select(e, { range }) { // Clones the start text node or returns an empty text node. function cloneStartTextNode(e) { const { ch1 } = getShorthandVars(e) - let textNode = { - types: {}, - props: { - children: "", - }, + if (!ch1.length) { + const textNode = { + types: {}, + props: { + children: "", + }, + } + return textNode } - const x = getIndexAtOffset(ch1, e.range.start.offset + testForSelection(e)) - if (x >= 0) { - textNode = JSONClone(ch1[x]) - } - return textNode + const x = getIndex(ch1, e.range.start.offset + testForSelection(e)) + return JSONClone(ch1[x]) } // Inserts text at the current range. diff --git a/src/Editor/useEditor/createIndexAtOffset.js b/src/Editor/useEditor/createIndexAtOffset.js index 6888a26..a27185c 100644 --- a/src/Editor/useEditor/createIndexAtOffset.js +++ b/src/Editor/useEditor/createIndexAtOffset.js @@ -1,8 +1,7 @@ import JSONClone from "lib/JSON/JSONClone" import textContent from "./textContent" -// Non-idempotent function; creates an index for an offset. -// See getIndexAtOffset for idempotent version. +// TODO function createIndexAtOffset(children, offset) { // Eager returns: if (!children.length) { diff --git a/src/Editor/useEditor/getIndex.js b/src/Editor/useEditor/getIndex.js new file mode 100644 index 0000000..ece08e0 --- /dev/null +++ b/src/Editor/useEditor/getIndex.js @@ -0,0 +1,29 @@ +import textContent from "./textContent" + +// Creates a text node at a text offset and returns the node +// offset for the created text node. + +// Converts a text offset to a node offset. +function getIndex(children, textOffset) { + if (!children.length) { + if (process.env.NODE_ENV !== "test") { + throw new Error("FIXME") + } + return -1 + } else if (!textOffset) { + return 0 + } else if (textOffset === textContent(children).length) { + return children.length - 1 + } + let nodeOffset = 0 + for (; nodeOffset < children.length; nodeOffset++) { + if (textOffset - children[nodeOffset].props.children.length <= 0) { + // No-op + break + } + textOffset -= children[nodeOffset].props.children.length + } + return nodeOffset +} + +export default getIndex diff --git a/src/Editor/useEditor/getIndex.test.js b/src/Editor/useEditor/getIndex.test.js new file mode 100644 index 0000000..fcefa57 --- /dev/null +++ b/src/Editor/useEditor/getIndex.test.js @@ -0,0 +1,25 @@ +import getIndex from "./getIndex" + +const children = [ + { types: {}, props: { children: "Hello, " } }, + { types: { code: {} }, props: { children: "world" } }, + { types: {}, props: { children: "!" } }, +] + +test("getIndex(...)", () => { + expect(getIndex([], 0)).toBe(-1) + expect(getIndex(children, 0)).toBe(0) + expect(getIndex(children, 1)).toBe(0) + expect(getIndex(children, 2)).toBe(0) + expect(getIndex(children, 3)).toBe(0) + expect(getIndex(children, 4)).toBe(0) + expect(getIndex(children, 5)).toBe(0) + expect(getIndex(children, 6)).toBe(0) + expect(getIndex(children, 7)).toBe(0) + expect(getIndex(children, 8)).toBe(1) + expect(getIndex(children, 9)).toBe(1) + expect(getIndex(children, 10)).toBe(1) + expect(getIndex(children, 11)).toBe(1) + expect(getIndex(children, 12)).toBe(1) + expect(getIndex(children, 13)).toBe(2) +}) diff --git a/src/Editor/useEditor/getIndexAtOffset.js b/src/Editor/useEditor/getIndexAtOffset.js deleted file mode 100644 index 4c67d17..0000000 --- a/src/Editor/useEditor/getIndexAtOffset.js +++ /dev/null @@ -1,25 +0,0 @@ -import textContent from "./textContent" - -// Idempotent function; gets an index for an offset. See -// createIndexAtOffset for non-idempotent version. -function getIndexAtOffset(children, offset) { - // Eager returns: - if (!children.length) { - return -1 - } else if (!offset) { - return 0 - } else if (offset === textContent(children).length) { - return children.length - 1 - } - let x = 0 - for (; x < children.length; x++) { - if (offset - children[x].props.children.length <= 0) { - // No-op - break - } - offset -= children[x].props.children.length - } - return x -} - -export default getIndexAtOffset diff --git a/src/Editor/useEditor/getIndexAtOffset.test.js b/src/Editor/useEditor/getIndexAtOffset.test.js deleted file mode 100644 index f1d3a8a..0000000 --- a/src/Editor/useEditor/getIndexAtOffset.test.js +++ /dev/null @@ -1,25 +0,0 @@ -import getIndexAtOffset from "./getIndexAtOffset" - -const children = [ - { types: {}, props: { children: "Hello, " } }, - { types: { code: {} }, props: { children: "world" } }, - { types: {}, props: { children: "!" } }, -] - -test("getIndexAtOffset(...)", () => { - expect(getIndexAtOffset([], 0)).toBe(-1) - expect(getIndexAtOffset(children, 0)).toBe(0) - expect(getIndexAtOffset(children, 1)).toBe(0) - expect(getIndexAtOffset(children, 2)).toBe(0) - expect(getIndexAtOffset(children, 3)).toBe(0) - expect(getIndexAtOffset(children, 4)).toBe(0) - expect(getIndexAtOffset(children, 5)).toBe(0) - expect(getIndexAtOffset(children, 6)).toBe(0) - expect(getIndexAtOffset(children, 7)).toBe(0) - expect(getIndexAtOffset(children, 8)).toBe(1) - expect(getIndexAtOffset(children, 9)).toBe(1) - expect(getIndexAtOffset(children, 10)).toBe(1) - expect(getIndexAtOffset(children, 11)).toBe(1) - expect(getIndexAtOffset(children, 12)).toBe(1) - expect(getIndexAtOffset(children, 13)).toBe(2) -}) diff --git a/src/Editor/useEditor/getRangeTypes.js b/src/Editor/useEditor/getRangeTypes.js index 61f86f6..56aaf7c 100644 --- a/src/Editor/useEditor/getRangeTypes.js +++ b/src/Editor/useEditor/getRangeTypes.js @@ -1,36 +1,27 @@ -import getIndexAtOffset from "./getIndexAtOffset" // TODO +import getIndex from "./getIndex" // TODO import getShorthandVars from "./getShorthandVars" import JSONClone from "lib/JSON/JSONClone" import JSONEqual from "lib/JSON/JSONEqual" import testForSelection from "./testForSelection" -function getNextIndexAtOffset(children, offset) { - if (!offset) { - return 0 - } - return getIndexAtOffset(children, offset) + 1 -} - -// Gets text nodes from an array of elements. -function getTextNodes(elements, range) { +function aggregateOnSelection(elements, range) { const ch = [] for (const each of elements) { - if (!each.props.children.length) { + const { key, props: { children } } = each + + if (!children.length) { // No-op continue } let x1 = 0 - if (each.key === range.start.key) { - x1 = getNextIndexAtOffset(each.props.children, range.start.offset) + if (key === range.start.key) { + x1 = getIndex(children, range.start.offset + 1) } - let x2 = each.props.children.length - if (each.key === range.end.key) { - x2 = getNextIndexAtOffset(each.props.children, range.end.offset) + let x2 = children.length + if (key === range.end.key) { + x2 = getIndex(children, range.end.offset) } - // if (x1 === x2) { - // x2++ - // } - ch.push(...each.props.children.slice(x1, x2 + (x1 === x2))) + ch.push(...children.slice(x1, x2 + 1)) } return ch } @@ -43,25 +34,24 @@ function getRangeTypes(e) { if (!ch1.length) { return {} } - const x = getIndexAtOffset(ch1, e.range.start.offset) + const x = getIndex(ch1, e.range.start.offset) return ch1[x].types } - const ch = getTextNodes(e.elements.slice(x1, x2 + 1), e.range) + const ch = aggregateOnSelection(e.elements.slice(x1, x2 + 1), e.range) if (!ch.length) { return {} } - const clonedTypes = JSONClone(ch[0].types) const clonedTypesKeys = Object.keys(clonedTypes) - for (const textNode of ch.slice(1)) { // Step over clonedTypes + + for (const textNode of ch.slice(1)) { // Steps over ch[0] for (const key of clonedTypesKeys) { if (!textNode.types[key] || !JSONEqual(textNode.types[key], clonedTypes[key])) { delete clonedTypes[key] } } } - return clonedTypes }