From 5418e260d46853df527c3db0ff1b1b82f0ca3060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20S=C3=B6rlin?= Date: Thu, 19 Dec 2019 17:51:44 +0100 Subject: [PATCH] moved getLeaves from react to slate core package and added some basic tests (#3358) --- packages/slate-react/src/components/text.tsx | 75 +------------------ packages/slate/src/interfaces/text.ts | 75 ++++++++++++++++++- .../test/interfaces/Text/decorations/end.js | 31 ++++++++ .../interfaces/Text/decorations/middle.js | 35 +++++++++ .../Text/decorations/overlapping.js | 49 ++++++++++++ .../test/interfaces/Text/decorations/start.js | 31 ++++++++ 6 files changed, 221 insertions(+), 75 deletions(-) create mode 100644 packages/slate/test/interfaces/Text/decorations/end.js create mode 100644 packages/slate/test/interfaces/Text/decorations/middle.js create mode 100644 packages/slate/test/interfaces/Text/decorations/overlapping.js create mode 100644 packages/slate/test/interfaces/Text/decorations/start.js diff --git a/packages/slate-react/src/components/text.tsx b/packages/slate-react/src/components/text.tsx index 6f7c5b139..4778ab775 100644 --- a/packages/slate-react/src/components/text.tsx +++ b/packages/slate-react/src/components/text.tsx @@ -25,7 +25,7 @@ const Text = (props: { const { decorations, isLast, parent, renderLeaf, text } = props const editor = useEditor() const ref = useRef(null) - const leaves = getLeaves(text, decorations) + const leaves = SlateText.decorations(text, decorations) const key = ReactEditor.findKey(editor, text) const children = [] @@ -63,79 +63,6 @@ const Text = (props: { ) } -/** - * Get the leaves for a text node given decorations. - */ - -const getLeaves = (node: SlateText, decorations: Range[]): SlateText[] => { - let leaves: SlateText[] = [{ ...node }] - - for (const dec of decorations) { - const { anchor, focus, ...rest } = dec - const [start, end] = Range.edges(dec) - const next = [] - let o = 0 - - for (const leaf of leaves) { - const { length } = leaf.text - const offset = o - o += length - - // If the range encompases the entire leaf, add the range. - if (start.offset <= offset && end.offset >= offset + length) { - Object.assign(leaf, rest) - next.push(leaf) - continue - } - - // If the range starts after the leaf, or ends before it, continue. - if ( - start.offset > offset + length || - end.offset < offset || - (end.offset === offset && offset !== 0) - ) { - next.push(leaf) - continue - } - - // Otherwise we need to split the leaf, at the start, end, or both, - // and add the range to the middle intersecting section. Do the end - // split first since we don't need to update the offset that way. - let middle = leaf - let before - let after - - if (end.offset < offset + length) { - const off = end.offset - offset - after = { ...middle, text: middle.text.slice(off) } - middle = { ...middle, text: middle.text.slice(0, off) } - } - - if (start.offset > offset) { - const off = start.offset - offset - before = { ...middle, text: middle.text.slice(0, off) } - middle = { ...middle, text: middle.text.slice(off) } - } - - Object.assign(middle, rest) - - if (before) { - next.push(before) - } - - next.push(middle) - - if (after) { - next.push(after) - } - } - - leaves = next - } - - return leaves -} - const MemoizedText = React.memo(Text, (prev, next) => { return ( next.parent === prev.parent && diff --git a/packages/slate/src/interfaces/text.ts b/packages/slate/src/interfaces/text.ts index a1e178e90..80ca65f22 100755 --- a/packages/slate/src/interfaces/text.ts +++ b/packages/slate/src/interfaces/text.ts @@ -1,5 +1,5 @@ import isPlainObject from 'is-plain-object' -import { Path } from '..' +import { Range } from '..' /** * `Text` objects represent the nodes that contain the actual text content of a @@ -83,4 +83,77 @@ export const Text = { return true }, + + /** + * Get the leaves for a text node given decorations. + */ + + decorations(node: Text, decorations: Range[]): Text[] { + let leaves: Text[] = [{ ...node }] + + for (const dec of decorations) { + const { anchor, focus, ...rest } = dec + const [start, end] = Range.edges(dec) + const next = [] + let o = 0 + + for (const leaf of leaves) { + const { length } = leaf.text + const offset = o + o += length + + // If the range encompases the entire leaf, add the range. + if (start.offset <= offset && end.offset >= offset + length) { + Object.assign(leaf, rest) + next.push(leaf) + continue + } + + // If the range starts after the leaf, or ends before it, continue. + if ( + start.offset > offset + length || + end.offset < offset || + (end.offset === offset && offset !== 0) + ) { + next.push(leaf) + continue + } + + // Otherwise we need to split the leaf, at the start, end, or both, + // and add the range to the middle intersecting section. Do the end + // split first since we don't need to update the offset that way. + let middle = leaf + let before + let after + + if (end.offset < offset + length) { + const off = end.offset - offset + after = { ...middle, text: middle.text.slice(off) } + middle = { ...middle, text: middle.text.slice(0, off) } + } + + if (start.offset > offset) { + const off = start.offset - offset + before = { ...middle, text: middle.text.slice(0, off) } + middle = { ...middle, text: middle.text.slice(off) } + } + + Object.assign(middle, rest) + + if (before) { + next.push(before) + } + + next.push(middle) + + if (after) { + next.push(after) + } + } + + leaves = next + } + + return leaves + }, } diff --git a/packages/slate/test/interfaces/Text/decorations/end.js b/packages/slate/test/interfaces/Text/decorations/end.js new file mode 100644 index 000000000..dadd76c7f --- /dev/null +++ b/packages/slate/test/interfaces/Text/decorations/end.js @@ -0,0 +1,31 @@ +import { Text } from 'slate' + +export const input = [ + { + anchor: { + path: [0], + offset: 2, + }, + focus: { + path: [0], + offset: 3, + }, + decoration: 'decoration', + }, +] + +export const test = decorations => { + return Text.decorations({ text: 'abc', mark: 'mark' }, decorations) +} + +export const output = [ + { + text: 'ab', + mark: 'mark', + }, + { + text: 'c', + mark: 'mark', + decoration: 'decoration', + }, +] diff --git a/packages/slate/test/interfaces/Text/decorations/middle.js b/packages/slate/test/interfaces/Text/decorations/middle.js new file mode 100644 index 000000000..983a61297 --- /dev/null +++ b/packages/slate/test/interfaces/Text/decorations/middle.js @@ -0,0 +1,35 @@ +import { Text } from 'slate' + +export const input = [ + { + anchor: { + path: [0], + offset: 1, + }, + focus: { + path: [0], + offset: 2, + }, + decoration: 'decoration', + }, +] + +export const test = decorations => { + return Text.decorations({ text: 'abc', mark: 'mark' }, decorations) +} + +export const output = [ + { + text: 'a', + mark: 'mark', + }, + { + text: 'b', + mark: 'mark', + decoration: 'decoration', + }, + { + text: 'c', + mark: 'mark', + }, +] diff --git a/packages/slate/test/interfaces/Text/decorations/overlapping.js b/packages/slate/test/interfaces/Text/decorations/overlapping.js new file mode 100644 index 000000000..2dd83d785 --- /dev/null +++ b/packages/slate/test/interfaces/Text/decorations/overlapping.js @@ -0,0 +1,49 @@ +import { Text } from 'slate' + +export const input = [ + { + anchor: { + path: [0], + offset: 1, + }, + focus: { + path: [0], + offset: 2, + }, + decoration1: 'decoration1', + }, + { + anchor: { + path: [0], + offset: 0, + }, + focus: { + path: [0], + offset: 3, + }, + decoration2: 'decoration2', + }, +] + +export const test = decorations => { + return Text.decorations({ text: 'abc', mark: 'mark' }, decorations) +} + +export const output = [ + { + text: 'a', + mark: 'mark', + decoration2: 'decoration2', + }, + { + text: 'b', + mark: 'mark', + decoration1: 'decoration1', + decoration2: 'decoration2', + }, + { + text: 'c', + mark: 'mark', + decoration2: 'decoration2', + }, +] diff --git a/packages/slate/test/interfaces/Text/decorations/start.js b/packages/slate/test/interfaces/Text/decorations/start.js new file mode 100644 index 000000000..53db2dfe4 --- /dev/null +++ b/packages/slate/test/interfaces/Text/decorations/start.js @@ -0,0 +1,31 @@ +import { Text } from 'slate' + +export const input = [ + { + anchor: { + path: [0], + offset: 0, + }, + focus: { + path: [0], + offset: 1, + }, + decoration: 'decoration', + }, +] + +export const test = decorations => { + return Text.decorations({ text: 'abc', mark: 'mark' }, decorations) +} + +export const output = [ + { + text: 'a', + mark: 'mark', + decoration: 'decoration', + }, + { + text: 'bc', + mark: 'mark', + }, +]